<?php
declare(strict_types=1);

/**
 * ventas_pedidos.php
 * ============================================================
 * BReport · MÓDULO VENTAS · PEDIDOS
 * ------------------------------------------------------------
 * Objetivo:
 * - Consultar pedidos comerciales registrados en sales_orders
 * - Ver detalle de artículos
 * - Cambiar estatus por parte del área de ventas
 * - Registrar bitácora de cambios en sales_order_status_log
 * - Consultar existencias disponibles por ubicación/planta
 * - Activar / desactivar clientes comerciales
 * - Activar / desactivar artículos
 *
 * Regla de negocio de inventario:
 * - BReport es MES multiempresa/multiplanta.
 * - Las existencias se consultan por empresa, planta/localidad y almacenes activos.
 * - Hielo Barra conserva reglas comerciales especiales cuando aplica.
 *
 * Notas:
 * - Este módulo vive en la raíz de BReport
 * - Ventas puede consultar inventario de todas las plantas
 *   activas de la empresa
 * ============================================================
 */

require_once __DIR__ . '/includes/auth.php';
require_once __DIR__ . '/includes/helpers.php';
require_once __DIR__ . '/includes/csrf.php';

require_scope();
ensure_session();

$pdo = pdo();
$u   = current_user();
$sc  = scope();

$companyId        = (int)($sc['company_id'] ?? 0);
$currentLocationId = (int)($sc['location_id'] ?? 0);

$canAccessVentas = has_role('admin')
    || has_role('sales')
    || has_role('ventas')
    || has_role('reparto')
    || has_role('supervisor')
    || has_role('corp')
    || has_role('plant_admin');

if (!$canAccessVentas) {
    http_response_code(403);
    exit('Acceso denegado. Este módulo requiere rol de ventas, reparto, supervisor o administrador.');
}

$canManage = has_role('admin')
    || has_role('sales')
    || has_role('ventas')
    || has_role('supervisor')
    || has_role('corp')
    || has_role('plant_admin');

$canSeeAllLocations = has_role('admin')
    || has_role('sales')
    || has_role('ventas')
    || has_role('supervisor')
    || has_role('corp')
    || has_role('plant_admin');

$action = (string)($_GET['action'] ?? '');

ensure_sales_module_schema($pdo);

/* ============================================================
   HELPERS
============================================================ */
function jexit(array $data, int $code = 200): void
{
    http_response_code($code);
    header('Content-Type: application/json; charset=utf-8');
    echo json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
    exit;
}

function read_json(): array
{
    $raw = file_get_contents('php://input');
    $arr = json_decode($raw ?: '[]', true);
    return is_array($arr) ? $arr : [];
}

function need_csrf(): void
{
    if (($_SERVER['REQUEST_METHOD'] ?? 'GET') === 'GET') {
        return;
    }

    $hdr = (string)($_SERVER['HTTP_X_CSRF'] ?? '');
    if ($hdr === '') {
        jexit(['ok' => false, 'error' => 'Falta encabezado X-CSRF'], 419);
    }

    csrf_verify($hdr);
}

function arr_get(array $arr, string $key, mixed $default = null): mixed
{
    return array_key_exists($key, $arr) ? $arr[$key] : $default;
}

/**
 * Las presentaciones comerciales se administran en piezas enteras.
 * No se redondea una captura del usuario: si viene decimal se rechaza para
 * impedir movimientos OUT fraccionarios en inventory_moves.
 */
function sales_is_whole_units(mixed $value): bool
{
    $qty = (float)$value;
    return abs($qty - round($qty)) <= 0.000001;
}

function sales_require_whole_units(mixed $value, string $label = 'Cantidad'): int
{
    $qty = (float)$value;
    if ($qty <= 0) {
        throw new RuntimeException($label . ' debe ser mayor a cero.');
    }
    if (!sales_is_whole_units($qty)) {
        throw new RuntimeException($label . ' solo permite piezas enteras; no se aceptan decimales.');
    }
    return (int)round($qty);
}

/**
 * Disponible comercial en piezas completas por almacén surtible.
 * Si existen residuos fraccionarios históricos, no se usan para surtir una
 * pieza nueva; deben corregirse mediante un ajuste real de inventario.
 */
function sales_available_whole_units(PDO $pdo, int $companyId, int $locationId, int $productId, string $sku): int
{
    $stockByWh = sales_product_warehouse_stock($pdo, $companyId, $locationId);
    $total = 0;
    foreach (sales_rule_warehouses_for_product($pdo, $companyId, $locationId, $productId, $sku, true) as $w) {
        $wid = (int)($w['warehouse_id'] ?? 0);
        $available = (float)($stockByWh[$productId][$wid] ?? 0);
        if ($available > 0) {
            $total += (int)floor($available + 0.000001);
        }
    }
    return $total;
}

function status_label(string $status): string
{
    return match (strtoupper($status)) {
        'PENDIENTE'   => 'Pendiente',
        'EN_REVISION' => 'En revisión',
        'AUTORIZADA'  => 'Autorizada',
        'RECHAZADA'   => 'Rechazada',
        'CANCELADA'   => 'Cancelada',
        'SURTIENDO'   => 'Surtiendo',
        'EN_RUTA'     => 'En ruta',
        'ENTREGADA'   => 'Entregada',
        default       => $status,
    };
}

function allowed_sales_statuses(): array
{
    return [
        'PENDIENTE',
        'EN_REVISION',
        'AUTORIZADA',
        'RECHAZADA',
        'CANCELADA',
        'SURTIENDO',
        'EN_RUTA',
        'ENTREGADA',
    ];
}

function allowed_sales_actions_from_ui(): array
{
    return [
        'EN_REVISION',
        'AUTORIZADA',
        'RECHAZADA',
        'CANCELADA',
    ];
}

function can_transition(string $oldStatus, string $newStatus): bool
{
    $oldStatus = strtoupper(trim($oldStatus));
    $newStatus = strtoupper(trim($newStatus));

    if ($oldStatus === $newStatus) {
        return false;
    }

    if (!in_array($newStatus, allowed_sales_actions_from_ui(), true)) {
        return false;
    }

    $map = [
        'PENDIENTE'   => ['EN_REVISION', 'AUTORIZADA', 'RECHAZADA', 'CANCELADA'],
        'EN_REVISION' => ['AUTORIZADA', 'RECHAZADA', 'CANCELADA'],
        'AUTORIZADA'  => ['CANCELADA'],
        'RECHAZADA'   => [],
        'CANCELADA'   => [],
        'SURTIENDO'   => [],
        'EN_RUTA'     => [],
        'ENTREGADA'   => [],
    ];

    return in_array($newStatus, $map[$oldStatus] ?? [], true);
}

function is_bar_sku(string $sku): bool
{
    return mb_strtoupper(trim($sku)) === 'BARRA 150 KILOS';
}

function is_bar_commercial_warehouse(string $code): bool
{
    $code = strtoupper(trim($code));
    return in_array($code, ['CF1', 'CF2'], true);
}

function get_bar_product_id(PDO $pdo, int $companyId): int
{
    $st = $pdo->prepare("
        SELECT id
        FROM products
        WHERE company_id = ? AND sku = 'BARRA 150 KILOS'
        LIMIT 1
    ");
    $st->execute([$companyId]);
    return (int)($st->fetchColumn() ?: 0);
}

function clamp_non_negative(float $value): float
{
    return $value < 0 ? 0.0 : $value;
}

function normalize_phone(string $phone): string
{
    $phone = trim($phone);
    $phone = preg_replace('/[^0-9+]/', '', $phone) ?? '';
    return substr($phone, 0, 30);
}

function customer_belongs_to_company(PDO $pdo, int $customerId, int $companyId): bool
{
    $st = $pdo->prepare("SELECT id FROM sales_customers WHERE id = ? AND company_id = ? LIMIT 1");
    $st->execute([$customerId, $companyId]);
    return (bool)$st->fetchColumn();
}


function sales_column_exists(PDO $pdo, string $table, string $column): bool
{
    $st = $pdo->prepare("
        SELECT COUNT(*)
        FROM information_schema.columns
        WHERE table_schema = DATABASE()
          AND table_name = ?
          AND column_name = ?
    ");
    $st->execute([$table, $column]);
    return (int)$st->fetchColumn() > 0;
}

function sales_table_exists(PDO $pdo, string $table): bool
{
    $st = $pdo->prepare("
        SELECT COUNT(*)
        FROM information_schema.tables
        WHERE table_schema = DATABASE()
          AND table_name = ?
    ");
    $st->execute([$table]);
    return (int)$st->fetchColumn() > 0;
}

function sales_pdf_url(int $orderId, int $outboundId = 0, string $folio = ''): string
{
    $url = './orden_entrega_pdf.php?order_id=' . $orderId;
    if ($outboundId > 0) {
        $url .= '&outbound_id=' . $outboundId;
    }
    if ($folio !== '') {
        $url .= '&folio=' . rawurlencode($folio);
    }
    return $url;
}



function sales_find_or_create_customer(PDO $pdo, int $companyId, int $locationId, string $businessName, string $phone = ''): int
{
    $businessName = trim($businessName);
    if ($businessName === '') {
        $businessName = 'Orden de salida Ventas';
    }
    $phone = normalize_phone($phone);

    $st = $pdo->prepare("
        SELECT id
        FROM sales_customers
        WHERE company_id = ?
          AND business_name = ?
          " . (sales_column_exists($pdo, 'sales_customers', 'location_id') ? "AND (location_id IS NULL OR location_id = ?)" : "") . "
        ORDER BY id DESC
        LIMIT 1
    ");
    $paramsFindCustomer = [$companyId, $businessName];
    if (sales_column_exists($pdo, 'sales_customers', 'location_id')) $paramsFindCustomer[] = $locationId;
    $st->execute($paramsFindCustomer);
    $id = (int)($st->fetchColumn() ?: 0);
    if ($id > 0) return $id;

    if (sales_column_exists($pdo, 'sales_customers', 'location_id')) {
        $st = $pdo->prepare("
            INSERT INTO sales_customers (company_id, location_id, business_name, contact_name, phone, active, created_at, updated_at)
            VALUES (?, ?, ?, ?, ?, 1, NOW(), NOW())
        ");
        $st->execute([$companyId, $locationId > 0 ? $locationId : null, $businessName, $businessName, $phone !== '' ? $phone : null]);
    } else {
        $st = $pdo->prepare("
            INSERT INTO sales_customers (company_id, business_name, contact_name, phone, active, created_at, updated_at)
            VALUES (?, ?, ?, ?, 1, NOW(), NOW())
        ");
        $st->execute([$companyId, $businessName, $businessName, $phone !== '' ? $phone : null]);
    }
    return (int)$pdo->lastInsertId();
}

function sales_validate_location(PDO $pdo, int $companyId, int $locationId): array
{
    $st = $pdo->prepare("
        SELECT id, name, code
        FROM locations
        WHERE id = ?
          AND company_id = ?
          AND active = 1
        LIMIT 1
    ");
    $st->execute([$locationId, $companyId]);
    $row = $st->fetch(PDO::FETCH_ASSOC);
    if (!$row) {
        throw new RuntimeException('Ubicación inválida o inactiva.');
    }
    return $row;
}

function sales_product_lookup(PDO $pdo, int $companyId, array $productIds): array
{
    $productIds = array_values(array_unique(array_filter(array_map('intval', $productIds), fn($v) => $v > 0)));
    if (!$productIds) return [];

    $ph = implode(',', array_fill(0, count($productIds), '?'));
    $params = array_merge([$companyId], $productIds);
    $st = $pdo->prepare("
        SELECT id, sku, name, uom, active
        FROM products
        WHERE company_id = ?
          AND id IN ($ph)
    ");
    $st->execute($params);

    $map = [];
    foreach ($st->fetchAll(PDO::FETCH_ASSOC) as $r) {
        $map[(int)$r['id']] = $r;
    }
    return $map;
}

function sales_default_channel_id(PDO $pdo, int $companyId): int
{
    if (!sales_table_exists($pdo, 'outbound_channels')) {
        return 0;
    }
    $st = $pdo->prepare("
        SELECT id
        FROM outbound_channels
        WHERE company_id = ? AND active = 1
        ORDER BY sort_order, id
        LIMIT 1
    ");
    $st->execute([$companyId]);
    return (int)($st->fetchColumn() ?: 0);
}


function sales_warehouse_id_by_code(PDO $pdo, int $companyId, int $locationId, string $code): int
{
    $st = $pdo->prepare("
        SELECT id
        FROM warehouses
        WHERE company_id = ?
          AND location_id = ?
          AND active = 1
          AND UPPER(code) = UPPER(?)
        LIMIT 1
    ");
    $st->execute([$companyId, $locationId, $code]);
    return (int)($st->fetchColumn() ?: 0);
}

function sales_product_warehouse_stock(PDO $pdo, int $companyId, int $locationId): array
{
    $st = $pdo->prepare("
        SELECT
            product_id,
            warehouse_id,
            COALESCE(SUM(CASE
                WHEN move_type IN ('IN','ADJ') THEN qty
                WHEN move_type = 'OUT' THEN -qty
                ELSE 0
            END),0) AS qty
        FROM inventory_moves
        WHERE company_id = ?
          AND location_id = ?
          AND warehouse_id IS NOT NULL
        GROUP BY product_id, warehouse_id
    ");
    $st->execute([$companyId, $locationId]);

    $out = [];
    foreach ($st->fetchAll(PDO::FETCH_ASSOC) as $r) {
        $pid = (int)$r['product_id'];
        $wid = (int)$r['warehouse_id'];
        if ($pid <= 0 || $wid <= 0) continue;
        $out[$pid][$wid] = (float)$r['qty'];
    }
    return $out;
}


function sales_location_context(PDO $pdo, int $companyId, int $locationId): array
{
    $companyName = '';
    $locationName = '';
    try {
        $st = $pdo->prepare("SELECT name FROM companies WHERE id=? LIMIT 1");
        $st->execute([$companyId]);
        $companyName = (string)($st->fetchColumn() ?: '');

        $st = $pdo->prepare("SELECT name FROM locations WHERE id=? AND company_id=? LIMIT 1");
        $st->execute([$locationId, $companyId]);
        $locationName = (string)($st->fetchColumn() ?: '');
    } catch (Throwable $e) {
        // No se bloquea ventas si falta descripción de alcance.
    }

    $label = mb_strtoupper(trim($companyName . ' ' . $locationName), 'UTF-8');
    $isHieloScope = (
        str_contains($label, 'HIELO')
        && str_contains($label, 'BARRA')
    );

    return [
        'company_name' => $companyName,
        'location_name' => $locationName,
        'is_hielo_scope' => $isHieloScope,
    ];
}

function sales_disposal_warehouse_codes(): array
{
    return ['MERMA', 'APROV-MERMA', 'SIN-WH'];
}

function sales_is_disposal_warehouse_code(string $code): bool
{
    return in_array(strtoupper(trim($code)), sales_disposal_warehouse_codes(), true);
}

function sales_active_warehouses(PDO $pdo, int $companyId, int $locationId, bool $excludeDisposal = true): array
{
    $st = $pdo->prepare("
        SELECT id, code, name
        FROM warehouses
        WHERE company_id = ?
          AND location_id = ?
          AND active = 1
        ORDER BY FIELD(UPPER(code),'CF1','CF2','CF3','ALM01','CUB','SOB-CUB','CUA','MERMA','APROV-MERMA'), code, name
    ");
    $st->execute([$companyId, $locationId]);

    $rows = [];
    foreach ($st->fetchAll(PDO::FETCH_ASSOC) as $w) {
        $code = strtoupper(trim((string)$w['code']));
        if ($excludeDisposal && sales_is_disposal_warehouse_code($code)) {
            continue;
        }
        $rows[] = [
            'warehouse_id' => (int)$w['id'],
            'warehouse_code' => (string)$w['code'],
            'warehouse_name' => (string)$w['name'],
        ];
    }
    return $rows;
}

function sales_warehouse_map(PDO $pdo, int $companyId, int $locationId): array
{
    $st = $pdo->prepare("
        SELECT id, code, name
        FROM warehouses
        WHERE company_id = ?
          AND location_id = ?
          AND active = 1
    ");
    $st->execute([$companyId, $locationId]);

    $map = [];
    foreach ($st->fetchAll(PDO::FETCH_ASSOC) as $w) {
        $map[(int)$w['id']] = [
            'warehouse_id' => (int)$w['id'],
            'warehouse_code' => (string)$w['code'],
            'warehouse_name' => (string)$w['name'],
            'code_upper' => strtoupper(trim((string)$w['code'])),
        ];
    }
    return $map;
}

function sales_warehouse_rows_by_ids(PDO $pdo, int $companyId, int $locationId, array $warehouseIds, bool $excludeDisposal = true): array
{
    $warehouseIds = array_values(array_unique(array_filter(array_map('intval', $warehouseIds), fn($v) => $v > 0)));
    if (!$warehouseIds) return [];

    $ph = implode(',', array_fill(0, count($warehouseIds), '?'));
    $params = array_merge([$companyId, $locationId], $warehouseIds);

    $st = $pdo->prepare("
        SELECT id, code, name
        FROM warehouses
        WHERE company_id = ?
          AND location_id = ?
          AND active = 1
          AND id IN ($ph)
        ORDER BY FIELD(UPPER(code),'CF1','CF2','CF3','ALM01','CUB','SOB-CUB','CUA','MERMA','APROV-MERMA'), code, name
    ");
    $st->execute($params);

    $rows = [];
    foreach ($st->fetchAll(PDO::FETCH_ASSOC) as $w) {
        $code = strtoupper(trim((string)$w['code']));
        if ($excludeDisposal && sales_is_disposal_warehouse_code($code)) {
            continue;
        }
        $rows[] = [
            'warehouse_id' => (int)$w['id'],
            'warehouse_code' => (string)$w['code'],
            'warehouse_name' => (string)$w['name'],
        ];
    }
    return $rows;
}

function sales_rule_warehouses_for_product(PDO $pdo, int $companyId, int $locationId, int $productId, string $sku, bool $forAvailability = true): array
{
    $sku = strtoupper(trim($sku));
    $ctx = sales_location_context($pdo, $companyId, $locationId);
    $ids = [];

    /*
     * Flujo exclusivo Hielo Barra:
     * - Barra 150 KILOS se consulta y surte únicamente desde CF1 / CF2.
     * - Producto terminado se consulta y surte únicamente desde CF3.
     * - CUB / SOB-CUB no son existencia comercial de venta.
     *
     * Esta regla se prioriza incluso si existe una configuración incorrecta,
     * para no afectar la operación funcional del flujo de Hielo Barra.
     */
    if (!empty($ctx['is_hielo_scope'])) {
        $preferredCodes = ($sku === 'BARRA 150 KILOS') ? ['CF1', 'CF2'] : ['CF3'];
        $preferredIds = [];
        foreach ($preferredCodes as $code) {
            $wid = sales_warehouse_id_by_code($pdo, $companyId, $locationId, $code);
            if ($wid > 0) $preferredIds[] = $wid;
        }
        return sales_warehouse_rows_by_ids($pdo, $companyId, $locationId, $preferredIds, true);
    }

    /*
     * Flujo MES de Botella, Hielo Bolsa, Garrafón y nuevas plantas:
     * primero respeta la configuración producto/localidad; si aún no existe,
     * toma los almacenes operativos activos creados para esa localidad.
     */
    if (sales_table_exists($pdo, 'product_warehouse_rules')) {
        $selectCols = "default_finished_warehouse_id, default_sales_warehouse_id";
        if (sales_column_exists($pdo, 'product_warehouse_rules', 'cubicado_warehouse_id')) {
            $selectCols .= ", cubicado_warehouse_id";
        } else {
            $selectCols .= ", NULL AS cubicado_warehouse_id";
        }

        $st = $pdo->prepare("
            SELECT {$selectCols}
            FROM product_warehouse_rules
            WHERE company_id = ?
              AND location_id = ?
              AND product_id = ?
              AND active = 1
            LIMIT 1
        ");
        $st->execute([$companyId, $locationId, $productId]);
        $rule = $st->fetch(PDO::FETCH_ASSOC);

        if ($rule) {
            $ids[] = (int)($rule['default_sales_warehouse_id'] ?? 0);
            $ids[] = (int)($rule['default_finished_warehouse_id'] ?? 0);
            if (!$forAvailability) {
                $ids[] = (int)($rule['cubicado_warehouse_id'] ?? 0);
            }

            $rows = sales_warehouse_rows_by_ids($pdo, $companyId, $locationId, $ids, true);
            if ($rows) return $rows;
        }
    }

    return sales_active_warehouses($pdo, $companyId, $locationId, true);
}

function sales_inventory_note(PDO $pdo, int $companyId, int $locationId): string
{
    $ctx = sales_location_context($pdo, $companyId, $locationId);
    if (!empty($ctx['is_hielo_scope'])) {
        return 'Inventario de venta para Hielo Barra: barra comercial CF1/CF2 y producto terminado según reglas de almacén.';
    }
    return 'Inventario de venta MES: existencias por almacenes activos de la planta seleccionada.';
}

function sales_allocate_inventory_out(PDO $pdo, int $companyId, int $locationId, int $productId, string $sku, float $qty): array
{
    /**
     * Asignación real de salida para pedidos de Ventas.
     * - Respeta empresa + planta/localidad del pedido.
     * - Usa product_warehouse_rules cuando existen.
     * - Si no hay regla, toma almacenes activos operativos de esa planta.
     * - Hielo Barra conserva compatibilidad comercial CF1/CF2 para barra y CF3 para terminado.
     */
    $qty = (float)sales_require_whole_units($qty, 'Cantidad a surtir');
    $sku = strtoupper(trim($sku));
    $stockByWh = sales_product_warehouse_stock($pdo, $companyId, $locationId);
    $candidateRows = sales_rule_warehouses_for_product($pdo, $companyId, $locationId, $productId, $sku, true);

    $alloc = [];
    $remaining = $qty;

    foreach ($candidateRows as $w) {
        $wid = (int)($w['warehouse_id'] ?? 0);
        if ($wid <= 0) continue;

        // Solo piezas completas pueden generar impacto real de salida.
        $available = (float)floor(max(0, (float)($stockByWh[$productId][$wid] ?? 0.0)) + 0.000001);
        if ($available <= 0) continue;

        $take = min($remaining, $available);
        if ($take > 0) {
            $alloc[] = [
                'warehouse_id' => $wid,
                'warehouse_code' => (string)($w['warehouse_code'] ?? ''),
                'qty' => round($take, 3),
            ];
            $remaining = round($remaining - $take, 3);
        }

        if ($remaining <= 0.0001) break;
    }

    if ($remaining > 0.0001) {
        $labels = array_filter(array_map(fn($w) => (string)($w['warehouse_code'] ?? ''), $candidateRows));
        $label = $labels ? implode('/', $labels) : 'almacenes activos de la planta';
        throw new RuntimeException(
            'Inventario insuficiente en ' . $label .
            ' para producto ID ' . $productId .
            '. Faltante: ' . number_format($remaining, 0, '.', '')
        );
    }

    return $alloc;
}

function ensure_sales_module_schema(PDO $pdo): void
{
    try {
        if (sales_table_exists($pdo, 'sales_orders') && !sales_column_exists($pdo, 'sales_orders', 'route_no')) {
            $pdo->exec("ALTER TABLE sales_orders ADD COLUMN route_no TINYINT UNSIGNED DEFAULT NULL AFTER requested_delivery_time");
        }
    } catch (Throwable $e) {}

    try {
        if (sales_table_exists($pdo, 'sales_customers') && !sales_column_exists($pdo, 'sales_customers', 'location_id')) {
            $pdo->exec("ALTER TABLE sales_customers ADD COLUMN location_id BIGINT(20) UNSIGNED DEFAULT NULL AFTER company_id");
        }
    } catch (Throwable $e) {}
}

function sales_route_label(?int $routeNo): string
{
    return ($routeNo !== null && $routeNo >= 1 && $routeNo <= 20) ? ('Ruta ' . $routeNo) : '';
}

function sales_route_conflict(PDO $pdo, int $companyId, int $locationId, string $deliveryDate, int $routeNo, int $excludeOrderId = 0): ?array
{
    if ($routeNo < 1 || $routeNo > 20 || !sales_column_exists($pdo, 'sales_orders', 'route_no')) return null;
    $st = $pdo->prepare("\n        SELECT id, sales_order_ref, status\n        FROM sales_orders\n        WHERE company_id = ? AND location_id = ? AND requested_delivery_date = ? AND route_no = ? AND id <> ?\n          AND status NOT IN ('CANCELADA','RECHAZADA','ENTREGADA')\n        ORDER BY id DESC\n        LIMIT 1\n    ");
    $st->execute([$companyId, $locationId, $deliveryDate, $routeNo, $excludeOrderId]);
    $row = $st->fetch(PDO::FETCH_ASSOC);
    return $row ?: null;
}

/**
 * Snapshot de inventario para consulta de ventas.
 * Regla:
 * - Barra comercial = solo CF1 + CF2
 * - CUB puede recibir barra para transformación, pero NO cuenta como inventario comercial
 * - CF3 almacena artículos convertidos, NO barra comercial
 */
function build_inventory_snapshot(PDO $pdo, int $companyId, int $locationId): array
{
    /**
     * Snapshot de inventario disponible para Ventas.
     * Fuente única oficial: inventory_moves.
     *
     * BReport no debe limitar ventas a CF1/CF2/CF3 para todas las plantas.
     * Cada pedido se consulta/surte con empresa + planta/localidad + almacenes
     * activos o reglas product_warehouse_rules de esa localidad.
     */
    $barId = get_bar_product_id($pdo, $companyId);
    $whMap = sales_warehouse_map($pdo, $companyId, $locationId);

    $st = $pdo->prepare("
        SELECT id, sku, name, uom, active
        FROM products
        WHERE company_id = ?
        ORDER BY sku
    ");
    $st->execute([$companyId]);
    $products = $st->fetchAll(PDO::FETCH_ASSOC);

    $rows = [];
    $allowedByProduct = [];

    foreach ($products as $p) {
        $pid = (int)$p['id'];
        $sku = (string)$p['sku'];
        $allowedRows = sales_rule_warehouses_for_product($pdo, $companyId, $locationId, $pid, $sku, true);
        $allowedIds = [];
        foreach ($allowedRows as $w) {
            $wid = (int)($w['warehouse_id'] ?? 0);
            if ($wid > 0) $allowedIds[$wid] = true;
        }
        $allowedByProduct[$pid] = $allowedIds;

        $rows[$pid] = [
            'product_id'  => $pid,
            'sku'         => $sku,
            'name'        => (string)$p['name'],
            'uom'         => (string)$p['uom'],
            'active'      => (int)$p['active'],
            'prod_cf1'    => 0.0,
            'prod_cf2'    => 0.0,
            'prod_cf3'    => 0.0,
            'prod_cub'    => 0.0,
            'cubing_in'   => 0.0,
            'quality_out' => 0.0,
            'manual_in'   => 0.0,
            'manual_out'  => 0.0,
            'on_hand'     => 0.0,
            'warehouse_breakdown' => [],
            'inventory_note' => sales_inventory_note($pdo, $companyId, $locationId),
        ];
    }

    $st = $pdo->prepare("
        SELECT
            im.product_id,
            im.warehouse_id,
            UPPER(im.move_type) AS move_type,
            SUM(im.qty) AS qty
        FROM inventory_moves im
        WHERE im.company_id = ?
          AND im.location_id = ?
          AND im.warehouse_id IS NOT NULL
        GROUP BY im.product_id, im.warehouse_id, UPPER(im.move_type)
    ");
    $st->execute([$companyId, $locationId]);

    foreach ($st->fetchAll(PDO::FETCH_ASSOC) as $r) {
        $pid = (int)$r['product_id'];
        if (!isset($rows[$pid])) continue;

        $wid  = (int)($r['warehouse_id'] ?? 0);
        $qty  = (float)$r['qty'];
        $typ  = strtoupper(trim((string)$r['move_type']));
        $code = (string)($whMap[$wid]['code_upper'] ?? '');
        $signed = ($typ === 'IN' || $typ === 'ADJ') ? $qty : (($typ === 'OUT') ? -$qty : 0.0);

        if ($code === 'CF1') $rows[$pid]['prod_cf1'] += $signed;
        if ($code === 'CF2') $rows[$pid]['prod_cf2'] += $signed;
        if ($code === 'CF3') {
            $rows[$pid]['prod_cf3'] += $signed;
            $rows[$pid]['cubing_in'] += $signed;
        }
        if ($code === 'CUB') $rows[$pid]['prod_cub'] += $signed;

        if (!isset($allowedByProduct[$pid][$wid])) {
            continue;
        }

        if (!isset($rows[$pid]['warehouse_breakdown'][$wid])) {
            $rows[$pid]['warehouse_breakdown'][$wid] = [
                'warehouse_id' => $wid,
                'warehouse_code' => (string)($whMap[$wid]['warehouse_code'] ?? ('WH-' . $wid)),
                'warehouse_name' => (string)($whMap[$wid]['warehouse_name'] ?? 'Almacén'),
                'qty' => 0.0,
            ];
        }

        $rows[$pid]['warehouse_breakdown'][$wid]['qty'] += $signed;
        $rows[$pid]['on_hand'] += $signed;

        if ($typ === 'IN' || $typ === 'ADJ') {
            $rows[$pid]['manual_in'] += $qty;
        } elseif ($typ === 'OUT') {
            $rows[$pid]['manual_out'] += $qty;
        }
    }

    foreach ($rows as &$row) {
        $row['warehouse_breakdown'] = array_values(array_filter(
            $row['warehouse_breakdown'],
            fn($w) => abs((float)($w['qty'] ?? 0)) > 0.0001
        ));
        usort($row['warehouse_breakdown'], fn($a, $b) => strcmp((string)$a['warehouse_code'], (string)$b['warehouse_code']));
    }
    unset($row);

    return [
        'rows' => array_values($rows),
        'bar_product_id' => $barId,
        'inventory_note' => sales_inventory_note($pdo, $companyId, $locationId),
    ];
}

/* ============================================================
   API
============================================================ */
if ($action !== '') {
    need_csrf();


    /*
     * BANDEJA COMERCIAL MES · SOLO CONSULTA
     * ------------------------------------------------------------
     * Devuelve pedidos por planta/localidad, sin costos, para la vista de
     * bandeja. No modifica el flujo existente de autorización o surtido.
     */
    if ($action === 'orders_mailbox') {
        $selectedLocationId = (int)($_GET['location_id'] ?? 0);
        $dateFrom = trim((string)($_GET['date_from'] ?? ''));
        $dateTo = trim((string)($_GET['date_to'] ?? ''));
        $customer = trim((string)($_GET['customer'] ?? ''));

        $where = ["o.company_id = ?"];
        $params = [$companyId];

        if (!$canSeeAllLocations) {
            $where[] = "o.location_id = ?";
            $params[] = $currentLocationId;
        } elseif ($selectedLocationId > 0) {
            $where[] = "o.location_id = ?";
            $params[] = $selectedLocationId;
        } else {
            // Por seguridad operativa la bandeja inicia con el scope actual.
            $where[] = "o.location_id = ?";
            $params[] = $currentLocationId;
        }

        if ($dateFrom !== '') {
            $where[] = "o.requested_delivery_date >= ?";
            $params[] = $dateFrom;
        }
        if ($dateTo !== '') {
            $where[] = "o.requested_delivery_date <= ?";
            $params[] = $dateTo;
        }
        if ($customer !== '') {
            $where[] = "(c.business_name LIKE ? OR c.contact_name LIKE ? OR o.sales_order_ref LIKE ?)";
            $params[] = '%' . $customer . '%';
            $params[] = '%' . $customer . '%';
            $params[] = '%' . $customer . '%';
        }

        $sql = "
            SELECT
                o.id,
                o.location_id,
                o.sales_order_ref,
                o.requested_delivery_date,
                o.requested_delivery_time,
                o.route_no,
                o.delivery_address,
                o.status,
                o.source,
                o.outbound_id,
                o.created_at,
                c.business_name,
                c.contact_name,
                c.phone,
                l.name AS location_name,
                l.code AS location_code,
                COUNT(soi.id) AS items_count,
                COALESCE(SUM(soi.qty), 0) AS total_qty
            FROM sales_orders o
            JOIN sales_customers c ON c.id = o.customer_id
            JOIN locations l ON l.id = o.location_id
            LEFT JOIN sales_order_items soi ON soi.order_id = o.id
            WHERE " . implode(' AND ', $where) . "
            GROUP BY
                o.id, o.location_id, o.sales_order_ref, o.requested_delivery_date,
                o.requested_delivery_time, o.route_no, o.delivery_address, o.status,
                o.source, o.outbound_id, o.created_at, c.business_name, c.contact_name,
                c.phone, l.name, l.code
            ORDER BY o.requested_delivery_date DESC, o.id DESC
            LIMIT 500
        ";
        $st = $pdo->prepare($sql);
        $st->execute($params);
        jexit(['ok'=>true, 'rows'=>$st->fetchAll(PDO::FETCH_ASSOC)]);
    }

    /*
     * SUGERENCIAS INTELIGENTES COMERCIALES · SOLO CONSULTA
     * ------------------------------------------------------------
     * Analiza pedidos y existencias surtibles de la planta seleccionada.
     * La existencia usa build_inventory_snapshot(), por lo que conserva:
     * - Hielo Barra: CF1/CF2 para barra y CF3 para terminado.
     * - Otras plantas: sus reglas y almacenes activos por localidad.
     */
    if ($action === 'sales_insights') {
        $selectedLocationId = (int)($_GET['location_id'] ?? 0);
        if (!$canSeeAllLocations || $selectedLocationId <= 0) {
            $selectedLocationId = $currentLocationId;
        }
        $loc = sales_validate_location($pdo, $companyId, $selectedLocationId);

        $dateTo = trim((string)($_GET['date_to'] ?? date('Y-m-d')));
        if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $dateTo)) $dateTo = date('Y-m-d');
        $dateFrom = trim((string)($_GET['date_from'] ?? ''));
        if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $dateFrom)) {
            $dateFrom = (new DateTimeImmutable($dateTo))->modify('-6 days')->format('Y-m-d');
        }
        if ($dateFrom > $dateTo) {
            $tmp = $dateFrom; $dateFrom = $dateTo; $dateTo = $tmp;
        }
        $days = max(1, (int)(new DateTimeImmutable($dateFrom))->diff(new DateTimeImmutable($dateTo))->days + 1);

        $st = $pdo->prepare("
            SELECT
                COUNT(DISTINCT o.id) AS orders_total,
                SUM(CASE WHEN o.status='AUTORIZADA' AND o.outbound_id IS NULL THEN 1 ELSE 0 END) AS pending_dispatch,
                SUM(CASE WHEN o.status IN ('PENDIENTE','EN_REVISION') THEN 1 ELSE 0 END) AS pending_review,
                SUM(CASE WHEN o.status IN ('SURTIENDO','EN_RUTA','ENTREGADA') OR o.outbound_id IS NOT NULL THEN 1 ELSE 0 END) AS fulfilled
            FROM sales_orders o
            WHERE o.company_id=?
              AND o.location_id=?
              AND o.requested_delivery_date BETWEEN ? AND ?
        ");
        $st->execute([$companyId, $selectedLocationId, $dateFrom, $dateTo]);
        $summary = $st->fetch(PDO::FETCH_ASSOC) ?: [];

        $st = $pdo->prepare("
            SELECT
                p.id AS product_id, p.sku, p.name AS product_name, p.uom,
                COALESCE(SUM(soi.qty),0) AS requested_qty,
                COUNT(DISTINCT o.id) AS order_count
            FROM sales_orders o
            JOIN sales_order_items soi ON soi.order_id=o.id
            JOIN products p ON p.id=soi.product_id
            WHERE o.company_id=?
              AND o.location_id=?
              AND o.status='AUTORIZADA'
              AND o.outbound_id IS NULL
            GROUP BY p.id, p.sku, p.name, p.uom
            ORDER BY requested_qty DESC
        ");
        $st->execute([$companyId, $selectedLocationId]);
        $pendingProducts = $st->fetchAll(PDO::FETCH_ASSOC);

        $snapshot = build_inventory_snapshot($pdo, $companyId, $selectedLocationId);
        $stockMap = [];
        foreach (($snapshot['rows'] ?? []) as $row) {
            $stockMap[(int)$row['product_id']] = (float)$row['on_hand'];
        }

        $risks = [];
        foreach ($pendingProducts as &$product) {
            $requested = (float)$product['requested_qty'];
            $available = (float)($stockMap[(int)$product['product_id']] ?? 0);
            $shortage = max(0.0, $requested - $available);
            $product['requested_qty'] = round($requested, 3);
            $product['available'] = round($available, 3);
            $product['shortage'] = round($shortage, 3);
            if ($shortage > 0.0001 || $available <= 0.0001) {
                $product['risk_level'] = $available <= 0.0001 ? 'critical' : 'warning';
                $risks[] = $product;
            }
        }
        unset($product);

        $st = $pdo->prepare("
            SELECT
                c.id AS customer_id,
                c.business_name,
                c.contact_name,
                COUNT(DISTINCT o.id) AS order_count,
                COALESCE(SUM(soi.qty),0) AS qty_requested
            FROM sales_orders o
            JOIN sales_customers c ON c.id=o.customer_id
            LEFT JOIN sales_order_items soi ON soi.order_id=o.id
            WHERE o.company_id=?
              AND o.location_id=?
              AND o.requested_delivery_date BETWEEN ? AND ?
              AND o.status NOT IN ('RECHAZADA','CANCELADA')
            GROUP BY c.id, c.business_name, c.contact_name
            ORDER BY order_count DESC, qty_requested DESC
            LIMIT 5
        ");
        $st->execute([$companyId, $selectedLocationId, $dateFrom, $dateTo]);
        $topCustomers = $st->fetchAll(PDO::FETCH_ASSOC);

        $recommendations = [];
        $pendingDispatch = (int)($summary['pending_dispatch'] ?? 0);
        $pendingReview = (int)($summary['pending_review'] ?? 0);

        if ($pendingDispatch > 0) {
            $recommendations[] = [
                'level'=>'info',
                'title'=>'Pedidos autorizados por surtir',
                'text'=>$pendingDispatch . ' pedido(s) autorizados esperan salida en ' . (string)$loc['name'] . '. Coordina con Almacén.'
            ];
        }
        if ($risks) {
            $first = $risks[0];
            $recommendations[] = [
                'level'=>($first['risk_level'] ?? 'warning'),
                'title'=>'Riesgo de surtido',
                'text'=>(string)$first['sku'] . ' requiere ' . number_format((float)$first['requested_qty'], 0, '.', '') .
                    ' y dispone de ' . number_format((float)$first['available'], 0, '.', '') .
                    '. Faltante: ' . number_format((float)$first['shortage'], 0, '.', '') . '.'
            ];
        }
        if ($pendingReview > 0) {
            $recommendations[] = [
                'level'=>'warning',
                'title'=>'Pedidos pendientes de revisión',
                'text'=>$pendingReview . ' pedido(s) requieren autorización, rechazo o cancelación para no detener la planeación.'
            ];
        }
        if ($topCustomers) {
            $top = $topCustomers[0];
            $name = trim((string)($top['business_name'] ?: $top['contact_name']));
            $recommendations[] = [
                'level'=>'success',
                'title'=>'Cliente con mayor actividad',
                'text'=>$name . ' registra ' . (int)$top['order_count'] . ' pedido(s) en el periodo por ' .
                    number_format((float)$top['qty_requested'], 0, '.', '') . ' piezas.'
            ];
        }
        if (!$recommendations) {
            $recommendations[] = [
                'level'=>'success',
                'title'=>'Operación comercial estable',
                'text'=>'No se detectan pedidos pendientes ni faltantes de inventario surtible en el periodo seleccionado.'
            ];
        }

        jexit([
            'ok'=>true,
            'location'=>$loc,
            'filters'=>['date_from'=>$dateFrom, 'date_to'=>$dateTo, 'days'=>$days],
            'summary'=>[
                'orders_total'=>(int)($summary['orders_total'] ?? 0),
                'pending_dispatch'=>$pendingDispatch,
                'pending_review'=>$pendingReview,
                'fulfilled'=>(int)($summary['fulfilled'] ?? 0),
                'risks'=>count($risks),
            ],
            'risks'=>array_slice($risks, 0, 5),
            'top_customers'=>$topCustomers,
            'recommendations'=>$recommendations,
            'inventory_note'=>$snapshot['inventory_note'] ?? sales_inventory_note($pdo, $companyId, $selectedLocationId),
        ]);
    }

    if ($action === 'orders') {
        $status            = trim((string)($_GET['status'] ?? ''));
        $dateFrom          = trim((string)($_GET['date_from'] ?? ''));
        $dateTo            = trim((string)($_GET['date_to'] ?? ''));
        $customer          = trim((string)($_GET['customer'] ?? ''));
        $selectedLocationId = (int)($_GET['location_id'] ?? 0);
        $view = strtolower(trim((string)($_GET['view'] ?? 'active')));

        $where = ["o.company_id = ?"];
        $params = [$companyId];

        if (!$GLOBALS['canSeeAllLocations']) {
            $where[] = "o.location_id = ?";
            $params[] = $GLOBALS['currentLocationId'];
        } elseif ($selectedLocationId > 0) {
            $where[] = "o.location_id = ?";
            $params[] = $selectedLocationId;
        }

        if ($status !== '') {
            $where[] = "o.status = ?";
            $params[] = $status;
        }

        if ($dateFrom !== '') {
            $where[] = "o.requested_delivery_date >= ?";
            $params[] = $dateFrom;
        }

        if ($dateTo !== '') {
            $where[] = "o.requested_delivery_date <= ?";
            $params[] = $dateTo;
        }

        if ($customer !== '') {
            $where[] = "(c.business_name LIKE ? OR c.contact_name LIKE ?)";
            $params[] = '%' . $customer . '%';
            $params[] = '%' . $customer . '%';
        }

        if ($view === 'history') {
            $where[] = "(o.outbound_id IS NOT NULL OR o.status IN ('SURTIENDO','EN_RUTA','ENTREGADA'))";
            $where[] = "(COALESCE(o.source,'') IN ('VentasHielo','VENTASHIELO','VH') OR o.sales_order_ref LIKE 'PW-%' OR o.outbound_id IS NOT NULL)";
        } elseif ($view === 'active') {
            $where[] = "(o.outbound_id IS NULL AND o.status NOT IN ('SURTIENDO','EN_RUTA','ENTREGADA'))";
        }

        $sql = "
            SELECT
                o.id,
                o.location_id,
                o.sales_order_ref,
                o.requested_delivery_date,
                o.requested_delivery_time,
                o.route_no,
                o.delivery_address,
                o.status,
                o.total,
                o.source,
                o.comments,
                o.outbound_id,
                o.created_at,
                c.id AS customer_id,
                c.business_name,
                c.contact_name,
                c.phone,
                cu.full_name AS customer_user_name,
                l.name AS location_name,
                l.code AS location_code,
                (
                    SELECT COUNT(*)
                    FROM sales_order_items soi
                    WHERE soi.order_id = o.id
                ) AS items_count
            FROM sales_orders o
            JOIN sales_customers c
              ON c.id = o.customer_id
            LEFT JOIN sales_customer_users cu
              ON cu.id = o.customer_user_id
            LEFT JOIN locations l
              ON l.id = o.location_id
            WHERE " . implode(' AND ', $where) . "
            ORDER BY o.id DESC
            LIMIT 400
        ";

        $st = $pdo->prepare($sql);
        $st->execute($params);

        jexit([
            'ok' => true,
            'rows' => $st->fetchAll(PDO::FETCH_ASSOC),
            'can_manage' => $canManage,
        ]);
    }

    if ($action === 'order_detail') {
        $orderId = (int)($_GET['order_id'] ?? 0);
        if ($orderId <= 0) {
            jexit(['ok' => false, 'error' => 'order_id requerido'], 400);
        }

        $sql = "
            SELECT
                o.*,
                c.business_name,
                c.contact_name,
                c.phone,
                cu.full_name AS customer_user_name,
                cu.phone AS customer_user_phone,
                ua.full_name AS approved_by_name,
                ur.full_name AS rejected_by_name,
                l.name AS location_name,
                l.code AS location_code
            FROM sales_orders o
            JOIN sales_customers c
              ON c.id = o.customer_id
            LEFT JOIN sales_customer_users cu
              ON cu.id = o.customer_user_id
            LEFT JOIN users ua
              ON ua.id = o.approved_by
            LEFT JOIN users ur
              ON ur.id = o.rejected_by
            LEFT JOIN locations l
              ON l.id = o.location_id
            WHERE o.id = ?
              AND o.company_id = ?
        ";
        $params = [$orderId, $companyId];

        if (!$canSeeAllLocations) {
            $sql .= " AND o.location_id = ?";
            $params[] = $currentLocationId;
        }

        $sql .= " LIMIT 1";

        $st = $pdo->prepare($sql);
        $st->execute($params);
        $order = $st->fetch(PDO::FETCH_ASSOC);

        if (!$order) {
            jexit(['ok' => false, 'error' => 'Pedido no encontrado'], 404);
        }

        $st = $pdo->prepare("
            SELECT
                i.id,
                i.order_id,
                i.product_id,
                i.qty,
                i.unit_price,
                i.line_total,
                i.notes,
                p.sku,
                p.name AS product_name,
                p.uom
            FROM sales_order_items i
            JOIN products p
              ON p.id = i.product_id
            WHERE i.order_id = ?
            ORDER BY p.sku
        ");
        $st->execute([$orderId]);
        $items = $st->fetchAll(PDO::FETCH_ASSOC);

        $st = $pdo->prepare("
            SELECT
                l.id,
                l.old_status,
                l.new_status,
                l.changed_by_user_id,
                l.changed_by_role,
                l.comment,
                l.created_at,
                u.full_name AS changed_by_name
            FROM sales_order_status_log l
            LEFT JOIN users u
              ON u.id = l.changed_by_user_id
            WHERE l.order_id = ?
            ORDER BY l.id DESC
        ");
        $st->execute([$orderId]);
        $logs = $st->fetchAll(PDO::FETCH_ASSOC);

        jexit([
            'ok'    => true,
            'order' => $order,
            'items' => $items,
            'logs'  => $logs,
        ]);
    }

    if ($action === 'status_update') {
        if (!$canManage) {
            jexit(['ok' => false, 'error' => 'Sin permiso'], 403);
        }

        $d = read_json();
        $orderId   = (int)arr_get($d, 'order_id', 0);
        $newStatus = strtoupper(trim((string)arr_get($d, 'new_status', '')));
        $comment   = trim((string)arr_get($d, 'comment', ''));

        if ($orderId <= 0) {
            jexit(['ok' => false, 'error' => 'order_id requerido'], 400);
        }

        if (!in_array($newStatus, allowed_sales_statuses(), true)) {
            jexit(['ok' => false, 'error' => 'Estatus inválido'], 400);
        }

        $pdo->beginTransaction();
        try {
            $sql = "
                SELECT *
                FROM sales_orders
                WHERE id = ?
                  AND company_id = ?
            ";
            $params = [$orderId, $companyId];

            if (!$canSeeAllLocations) {
                $sql .= " AND location_id = ?";
                $params[] = $currentLocationId;
            }

            $sql .= " FOR UPDATE";

            $st = $pdo->prepare($sql);
            $st->execute($params);
            $order = $st->fetch(PDO::FETCH_ASSOC);

            if (!$order) {
                throw new RuntimeException('Pedido no encontrado');
            }

            $oldStatus = strtoupper((string)$order['status']);

            if (!can_transition($oldStatus, $newStatus)) {
                throw new RuntimeException("No se puede cambiar de {$oldStatus} a {$newStatus}");
            }

            $approvedBy = null;
            $approvedAt = null;
            $rejectedBy = null;
            $rejectedAt = null;

            if ($newStatus === 'AUTORIZADA') {
                $approvedBy = (int)$GLOBALS['u']['id'];
                $approvedAt = date('Y-m-d H:i:s');
            }

            if ($newStatus === 'RECHAZADA') {
                $rejectedBy = (int)$GLOBALS['u']['id'];
                $rejectedAt = date('Y-m-d H:i:s');
            }

            $st = $pdo->prepare("
                UPDATE sales_orders
                SET
                    status = ?,
                    approved_by = CASE WHEN ? IS NULL THEN approved_by ELSE ? END,
                    approved_at = CASE WHEN ? IS NULL THEN approved_at ELSE ? END,
                    rejected_by = CASE WHEN ? IS NULL THEN rejected_by ELSE ? END,
                    rejected_at = CASE WHEN ? IS NULL THEN rejected_at ELSE ? END,
                    updated_at = NOW()
                WHERE id = ?
            ");
            $st->execute([
                $newStatus,
                $approvedBy, $approvedBy,
                $approvedAt, $approvedAt,
                $rejectedBy, $rejectedBy,
                $rejectedAt, $rejectedAt,
                $orderId,
            ]);

            $st = $pdo->prepare("
                INSERT INTO sales_order_status_log (
                    order_id,
                    old_status,
                    new_status,
                    changed_by_user_id,
                    changed_by_role,
                    comment,
                    created_at
                ) VALUES (?, ?, ?, ?, ?, ?, NOW())
            ");
            $st->execute([
                $orderId,
                $oldStatus,
                $newStatus,
                (int)$u['id'],
                'VENTAS',
                ($comment === '' ? null : $comment),
            ]);

            $pdo->commit();
            jexit(['ok' => true]);
        } catch (Throwable $e) {
            if ($pdo->inTransaction()) {
                $pdo->rollBack();
            }
            jexit(['ok' => false, 'error' => $e->getMessage()], 500);
        }
    }




    if ($action === 'manual_order_create') {
        if (!$canManage) jexit(['ok' => false, 'error' => 'Sin permiso'], 403);

        $d = read_json();
        $orderId = (int)arr_get($d, 'order_id', 0);
        $selectedLocationId = (int)arr_get($d, 'location_id', 0);
        $routeNo = (int)arr_get($d, 'route_no', 0);
        $customerName = trim((string)arr_get($d, 'customer_name', ''));
        $customerPhone = trim((string)arr_get($d, 'customer_phone', ''));
        $deliveryDate = trim((string)arr_get($d, 'requested_delivery_date', date('Y-m-d')));
        $deliveryTime = trim((string)arr_get($d, 'requested_delivery_time', ''));
        $deliveryAddress = trim((string)arr_get($d, 'delivery_address', ''));
        $comments = trim((string)arr_get($d, 'comments', ''));
        $itemsRaw = arr_get($d, 'items', []);

        if ($selectedLocationId <= 0) jexit(['ok' => false, 'error' => 'Selecciona la planta / ubicación.'], 400);
        if (!$canSeeAllLocations && $selectedLocationId !== $currentLocationId) jexit(['ok' => false, 'error' => 'No tienes permiso para generar pedidos en esta ubicación.'], 403);
        if ($routeNo < 1 || $routeNo > 20) jexit(['ok' => false, 'error' => 'Selecciona una ruta válida de la 1 a la 20.'], 400);
        if ($deliveryDate === '') $deliveryDate = date('Y-m-d');
        if ($customerName === '') $customerName = 'Orden de salida Ventas';
        if ($deliveryAddress === '') $deliveryAddress = 'Solicitud generada desde módulo Ventas';
        if (!is_array($itemsRaw) || !$itemsRaw) jexit(['ok' => false, 'error' => 'Agrega al menos un artículo.'], 400);

        $cleanItems = [];
        foreach ($itemsRaw as $it) {
            $pid = (int)($it['product_id'] ?? 0);
            $qtyRaw = (float)($it['qty'] ?? 0);
            if ($pid <= 0 || $qtyRaw <= 0) continue;
            if (!sales_is_whole_units($qtyRaw)) {
                jexit(['ok' => false, 'error' => 'La cantidad de cada artículo debe ser una pieza entera. No se permiten decimales.'], 400);
            }
            $qty = (int)round($qtyRaw);
            $cleanItems[$pid] = (int)($cleanItems[$pid] ?? 0) + $qty;
        }
        if (!$cleanItems) jexit(['ok' => false, 'error' => 'Los artículos capturados no son válidos.'], 400);

        $pdo->beginTransaction();
        try {
            sales_validate_location($pdo, $companyId, $selectedLocationId);
            $conflict = sales_route_conflict($pdo, $companyId, $selectedLocationId, $deliveryDate, $routeNo, $orderId);
            if ($conflict) {
                $conflictFolio = trim((string)($conflict['sales_order_ref'] ?? ''));
                if ($conflictFolio === '') $conflictFolio = 'PED-' . (int)$conflict['id'];
                throw new RuntimeException('Ya existe un pedido para ' . sales_route_label($routeNo) . ' en la fecha ' . $deliveryDate . ' (' . $conflictFolio . '). Puedes editar ese pedido, pero no crear otro para la misma ruta.');
            }

            $products = sales_product_lookup($pdo, $companyId, array_keys($cleanItems));
            if (!$products) throw new RuntimeException('No se encontraron artículos válidos para la empresa.');
            foreach ($cleanItems as $pid => $qty) {
                if (!isset($products[$pid])) throw new RuntimeException('Uno de los artículos seleccionados no existe en BReport.');
                if ((int)($products[$pid]['active'] ?? 0) !== 1) throw new RuntimeException('El artículo ' . (string)$products[$pid]['sku'] . ' está inactivo.');
            }

            $customerId = sales_find_or_create_customer($pdo, $companyId, $selectedLocationId, $customerName, $customerPhone);
            $commentParts = ['Orden de salida creada desde módulo Ventas.', sales_route_label($routeNo)];
            if ($comments !== '') $commentParts[] = $comments;
            $fullComments = implode("\n", $commentParts);
            $isUpdate = $orderId > 0;

            if ($isUpdate) {
                $sql = "SELECT * FROM sales_orders WHERE id = ? AND company_id = ?";
                $params = [$orderId, $companyId];
                if (!$canSeeAllLocations) { $sql .= " AND location_id = ?"; $params[] = $currentLocationId; }
                $sql .= " FOR UPDATE";
                $st = $pdo->prepare($sql);
                $st->execute($params);
                $existing = $st->fetch(PDO::FETCH_ASSOC);
                if (!$existing) throw new RuntimeException('Pedido no encontrado para editar.');
                if ((int)($existing['outbound_id'] ?? 0) > 0) throw new RuntimeException('No se puede editar un pedido que ya tiene salida generada.');
                if (strtoupper((string)($existing['source'] ?? '')) !== 'VENTAS') throw new RuntimeException('Solo se pueden editar órdenes creadas desde Ventas.');
                if (!in_array(strtoupper((string)$existing['status']), ['AUTORIZADA','PENDIENTE','EN_REVISION'], true)) throw new RuntimeException('El estatus actual no permite edición.');
                $st = $pdo->prepare("UPDATE sales_orders SET location_id=?, customer_id=?, requested_delivery_date=?, requested_delivery_time=?, route_no=?, delivery_address=?, comments=?, status='AUTORIZADA', total=0, approved_by=?, approved_at=NOW(), updated_at=NOW() WHERE id=?");
                $st->execute([$selectedLocationId, $customerId, $deliveryDate, ($deliveryTime !== '' ? $deliveryTime : null), $routeNo, $deliveryAddress, $fullComments, (int)$u['id'], $orderId]);
                $st = $pdo->prepare("DELETE FROM sales_order_items WHERE order_id = ?");
                $st->execute([$orderId]);
                $folio = trim((string)($existing['sales_order_ref'] ?? ''));
                if ($folio === '') { $folio = 'OSV-' . str_pad((string)$orderId, 6, '0', STR_PAD_LEFT); $pdo->prepare("UPDATE sales_orders SET sales_order_ref=? WHERE id=?")->execute([$folio, $orderId]); }
            } else {
                $st = $pdo->prepare("INSERT INTO sales_orders (company_id, location_id, customer_id, source, requested_delivery_date, requested_delivery_time, route_no, delivery_address, comments, status, total, approved_by, approved_at, created_by, created_at, updated_at) VALUES (?, ?, ?, 'VENTAS', ?, ?, ?, ?, ?, 'AUTORIZADA', 0, ?, NOW(), ?, NOW(), NOW())");
                $st->execute([$companyId, $selectedLocationId, $customerId, $deliveryDate, ($deliveryTime !== '' ? $deliveryTime : null), $routeNo, $deliveryAddress, $fullComments, (int)$u['id'], (int)$u['id']]);
                $orderId = (int)$pdo->lastInsertId();
                $folio = 'OSV-' . str_pad((string)$orderId, 6, '0', STR_PAD_LEFT);
                $pdo->prepare("UPDATE sales_orders SET sales_order_ref=? WHERE id=?")->execute([$folio, $orderId]);
            }

            $stItem = $pdo->prepare("INSERT INTO sales_order_items (order_id, product_id, qty, unit_price, line_total, notes, created_at) VALUES (?, ?, ?, 0, 0, ?, NOW())");
            foreach ($cleanItems as $pid => $qty) $stItem->execute([$orderId, (int)$pid, (float)$qty, 'Orden de salida creada por Ventas']);

            if (sales_table_exists($pdo, 'sales_order_status_log')) {
                $st = $pdo->prepare("INSERT INTO sales_order_status_log (order_id, old_status, new_status, changed_by_user_id, changed_by_role, comment, created_at) VALUES (?, ?, ?, ?, ?, ?, NOW())");
                $st->execute([$orderId, $isUpdate ? 'AUTORIZADA' : null, 'AUTORIZADA', (int)$u['id'], 'VENTAS', $isUpdate ? 'Orden de salida editada desde Ventas.' : 'Orden de salida creada desde Ventas. Visible para Almacén.']);
            }

            $pdo->commit();
            jexit(['ok' => true, 'order_id' => $orderId, 'folio' => $folio, 'route_no' => $routeNo, 'message' => $isUpdate ? 'Orden de salida actualizada correctamente.' : 'Orden de salida creada correctamente. Almacén ya puede verla para surtirla.']);
        } catch (Throwable $e) {
            if ($pdo->inTransaction()) $pdo->rollBack();
            jexit(['ok' => false, 'error' => $e->getMessage()], 500);
        }
    }

    if ($action === 'sales_dispatch_from_sales') {
        if (!$canManage) {
            jexit(['ok' => false, 'error' => 'Sin permiso'], 403);
        }

        $d = read_json();
        $orderId = (int)arr_get($d, 'order_id', 0);
        $workDate = trim((string)arr_get($d, 'work_date', date('Y-m-d')));
        $notes = trim((string)arr_get($d, 'notes', ''));

        if ($orderId <= 0) {
            jexit(['ok' => false, 'error' => 'order_id requerido'], 400);
        }

        $pdo->beginTransaction();
        try {
            $sql = "
                SELECT
                    o.*,
                    c.business_name,
                    c.contact_name,
                    c.phone
                FROM sales_orders o
                JOIN sales_customers c ON c.id = o.customer_id
                WHERE o.id = ?
                  AND o.company_id = ?
            ";
            $params = [$orderId, $companyId];

            if (!$canSeeAllLocations) {
                $sql .= " AND o.location_id = ?";
                $params[] = $currentLocationId;
            }
            $sql .= " FOR UPDATE";

            $st = $pdo->prepare($sql);
            $st->execute($params);
            $order = $st->fetch(PDO::FETCH_ASSOC);

            if (!$order) {
                throw new RuntimeException('Pedido no encontrado.');
            }

            $oldStatus = strtoupper((string)$order['status']);
            $folio = trim((string)($order['sales_order_ref'] ?? ''));
            if ($folio === '') {
                $folio = 'PED-' . (int)$order['id'];
            }

            if (!empty($order['outbound_id'])) {
                $existingOutboundId = (int)$order['outbound_id'];
                $pdo->commit();
                jexit([
                    'ok' => true,
                    'already_processed' => true,
                    'message' => 'El pedido ya tiene salida generada.',
                    'order_id' => $orderId,
                    'outbound_id' => $existingOutboundId,
                    'pdf_url' => sales_pdf_url($orderId, $existingOutboundId, $folio),
                    'warnings' => [],
                ]);
            }

            if ($oldStatus !== 'AUTORIZADA') {
                throw new RuntimeException('Solo se pueden generar salidas de pedidos AUTORIZADA. Estatus actual: ' . $oldStatus);
            }

            $orderLocationId = (int)$order['location_id'];
            $channelId = sales_default_channel_id($pdo, $companyId);
            if ($channelId <= 0) {
                throw new RuntimeException('No existe un canal de salida activo para la empresa. Configura outbound_channels.');
            }

            $st = $pdo->prepare("
                SELECT
                    i.product_id,
                    SUM(i.qty) AS qty,
                    MAX(p.sku) AS sku,
                    MAX(p.name) AS product_name,
                    MAX(p.uom) AS uom
                FROM sales_order_items i
                JOIN products p ON p.id = i.product_id
                WHERE i.order_id = ?
                GROUP BY i.product_id
                ORDER BY MAX(p.sku), MAX(p.name)
            ");
            $st->execute([$orderId]);
            $items = $st->fetchAll(PDO::FETCH_ASSOC);

            if (!$items) {
                throw new RuntimeException('El pedido no tiene artículos.');
            }

            $dispatchItems = [];
            $warnings = [];
            foreach ($items as $it) {
                $pid = (int)$it['product_id'];
                $requestedRaw = (float)$it['qty'];

                if ($requestedRaw <= 0) {
                    continue;
                }
                if (!sales_is_whole_units($requestedRaw)) {
                    throw new RuntimeException('El pedido contiene una cantidad decimal en ' . (string)$it['sku'] . '. Edita el pedido y captura piezas enteras antes de generar la salida.');
                }

                $requestedQty = (float)(int)round($requestedRaw);
                $stock = (float)sales_available_whole_units($pdo, $companyId, $orderLocationId, $pid, (string)$it['sku']);

                if ($stock <= 0) {
                    $warnings[] = 'Sin existencia para ' . (string)$it['sku'] . ' - ' . (string)$it['product_name'] . '. No se descontó.';
                    continue;
                }

                $qtyToDispatch = min($requestedQty, $stock);
                if ($qtyToDispatch < $requestedQty) {
                    $warnings[] = 'Existencia parcial para ' . (string)$it['sku'] . ' - ' . (string)$it['product_name'] . '. Solicitado: ' . number_format($requestedQty, 0, '.', '') . ', descontado: ' . number_format($qtyToDispatch, 0, '.', '') . '.';
                }

                $dispatchItems[] = [
                    'product_id' => $pid,
                    'qty' => $qtyToDispatch,
                    'sku' => (string)$it['sku'],
                    'product_name' => (string)$it['product_name'],
                ];
            }

            if (!$dispatchItems) {
                throw new RuntimeException('No hay artículos con existencia disponible para generar la salida.');
            }

            $notesExtra = [];
            $notesExtra[] = 'Salida generada desde módulo Ventas';
            $notesExtra[] = 'Pedido: ' . $folio;
            if (!empty($order['business_name'])) $notesExtra[] = 'Cliente: ' . (string)$order['business_name'];
            if ($notes !== '') $notesExtra[] = 'Notas ventas: ' . $notes;
            if ($warnings) $notesExtra[] = 'Advertencias: ' . implode(' / ', $warnings);
            $fullNotes = implode(' | ', $notesExtra);

            $st = $pdo->prepare("
                INSERT INTO outbounds (
                    company_id, location_id, outbound_date, channel_id, status,
                    created_by, reference, notes
                ) VALUES (
                    ?, ?, ?, ?, 'POSTED',
                    ?, ?, ?
                )
            ");
            $st->execute([
                $companyId,
                $orderLocationId,
                $workDate,
                $channelId,
                (int)$u['id'],
                $folio,
                $fullNotes,
            ]);
            $outboundId = (int)$pdo->lastInsertId();

            $stItem = $pdo->prepare("
                INSERT INTO outbound_items (outbound_id, product_id, qty)
                VALUES (?, ?, ?)
            ");

            $stMove = $pdo->prepare("
                INSERT INTO inventory_moves (
                    company_id, location_id, warehouse_id, work_date, product_id, qty, move_type,
                    channel_id, ref_type, ref_id, notes, created_by, created_at
                ) VALUES (
                    ?, ?, ?, ?, ?, ?, 'OUT',
                    ?, 'OUTBOUND', ?, ?, ?, NOW()
                )
            ");

            $movementSummary = [];
            foreach ($dispatchItems as $it) {
                $productIdMove = (int)$it['product_id'];
                $qtyMove = (float)$it['qty'];
                $stItem->execute([$outboundId, $productIdMove, $qtyMove]);

                foreach (sales_allocate_inventory_out($pdo, $companyId, $orderLocationId, $productIdMove, (string)$it['sku'], $qtyMove) as $alloc) {
                    $moveNotes = $fullNotes . ' | WH: ' . (string)$alloc['warehouse_code'];
                    $stMove->execute([
                        $companyId,
                        $orderLocationId,
                        (int)$alloc['warehouse_id'],
                        $workDate,
                        $productIdMove,
                        (float)$alloc['qty'],
                        $channelId,
                        $outboundId,
                        $moveNotes,
                        (int)$u['id'],
                    ]);
                    $movementSummary[] = [
                        'product_id' => $productIdMove,
                        'sku' => (string)$it['sku'],
                        'product_name' => (string)$it['product_name'],
                        'warehouse_id' => (int)$alloc['warehouse_id'],
                        'warehouse_code' => (string)$alloc['warehouse_code'],
                        'qty' => (float)$alloc['qty'],
                        'move_type' => 'OUT',
                        'ref_type' => 'OUTBOUND',
                    ];
                }
            }

            $updateCols = [
                'outbound_id = ?',
                "status = 'SURTIENDO'",
                'updated_at = NOW()',
            ];
            $updateParams = [$outboundId];
            if (sales_column_exists($pdo, 'sales_orders', 'fulfilled_at')) {
                $updateCols[] = 'fulfilled_at = NOW()';
            }
            $updateParams[] = $orderId;
            $st = $pdo->prepare('UPDATE sales_orders SET ' . implode(', ', $updateCols) . ' WHERE id = ?');
            $st->execute($updateParams);

            if (sales_table_exists($pdo, 'sales_order_status_log')) {
                $st = $pdo->prepare("
                    INSERT INTO sales_order_status_log (
                        order_id, old_status, new_status, changed_by_user_id, changed_by_role, comment, created_at
                    ) VALUES (?, ?, ?, ?, ?, ?, NOW())
                ");
                $st->execute([
                    $orderId,
                    $oldStatus,
                    'SURTIENDO',
                    (int)$u['id'],
                    'VENTAS',
                    'Salida generada desde Ventas. Outbound #' . $outboundId,
                ]);
            }

            $pdo->commit();
            jexit([
                'ok' => true,
                'order_id' => $orderId,
                'outbound_id' => $outboundId,
                'pdf_url' => sales_pdf_url($orderId, $outboundId, $folio),
                'warnings' => $warnings,
                'dispatched_items' => $dispatchItems,
                'movements' => $movementSummary ?? [],
                'message' => 'Salida generada correctamente.',
            ]);
        } catch (Throwable $e) {
            if ($pdo->inTransaction()) {
                $pdo->rollBack();
            }
            jexit(['ok' => false, 'error' => $e->getMessage()], 500);
        }
    }

    if ($action === 'inventory_locations' || $action === 'location_options') {
        $st = $pdo->prepare("
            SELECT id, name, code
            FROM locations
            WHERE company_id = ?
              AND active = 1
            ORDER BY name
        ");
        $st->execute([$companyId]);

        jexit([
            'ok' => true,
            'rows' => $st->fetchAll(PDO::FETCH_ASSOC)
        ]);
    }

    if ($action === 'inventory_by_location') {
        $selectedLocationId = (int)($_GET['location_id'] ?? 0);
        if ($selectedLocationId <= 0) {
            jexit(['ok' => false, 'error' => 'location_id requerido'], 400);
        }

        $st = $pdo->prepare("
            SELECT id, name, code
            FROM locations
            WHERE id = ?
              AND company_id = ?
              AND active = 1
            LIMIT 1
        ");
        $st->execute([$selectedLocationId, $companyId]);
        $loc = $st->fetch(PDO::FETCH_ASSOC);

        if (!$loc) {
            jexit(['ok' => false, 'error' => 'Ubicación no encontrada'], 404);
        }

        $snap = build_inventory_snapshot($pdo, $companyId, $selectedLocationId);
        $rows = $snap['rows'];

        usort($rows, function(array $a, array $b): int {
            return strcmp((string)$a['sku'], (string)$b['sku']);
        });

        jexit([
            'ok'       => true,
            'location' => $loc,
            'rows'     => $rows,
            'inventory_note' => $snap['inventory_note'] ?? sales_inventory_note($pdo, $companyId, $selectedLocationId),
        ]);
    }

    if ($action === 'customers') {
        if (!$canManage) jexit(['ok' => false, 'error' => 'Sin permiso'], 403);
        $q = trim((string)($_GET['q'] ?? ''));
        $selectedLocationId = (int)($_GET['location_id'] ?? $currentLocationId);
        $where = ["company_id = ?"];
        $params = [$companyId];
        if (sales_column_exists($pdo, 'sales_customers', 'location_id')) {
            $loc = $selectedLocationId > 0 ? $selectedLocationId : $currentLocationId;
            if ($loc > 0) { $where[] = "(location_id IS NULL OR location_id = ?)"; $params[] = $loc; }
        }
        if ($q !== '') {
            $where[] = "(business_name LIKE ? OR contact_name LIKE ? OR phone LIKE ? OR email LIKE ?)";
            $params[] = '%' . $q . '%'; $params[] = '%' . $q . '%'; $params[] = '%' . $q . '%'; $params[] = '%' . $q . '%';
        }
        $locSelect = sales_column_exists($pdo, 'sales_customers', 'location_id') ? 'location_id,' : 'NULL AS location_id,';
        $sql = "SELECT id, {$locSelect} customer_code, business_name, contact_name, phone, email, address, postal_code, active, created_at FROM sales_customers WHERE " . implode(' AND ', $where) . " ORDER BY active DESC, business_name ASC";
        $st = $pdo->prepare($sql);
        $st->execute($params);
        jexit(['ok' => true, 'rows' => $st->fetchAll(PDO::FETCH_ASSOC)]);
    }

    if ($action === 'customer_save') {
        if (!$canManage) jexit(['ok' => false, 'error' => 'Sin permiso'], 403);
        $d = read_json();
        $customerId = (int)arr_get($d, 'customer_id', 0);
        $locationId = (int)arr_get($d, 'location_id', $currentLocationId);
        $businessName = trim((string)arr_get($d, 'business_name', ''));
        $contactName = trim((string)arr_get($d, 'contact_name', ''));
        $phone = normalize_phone((string)arr_get($d, 'phone', ''));
        $email = trim((string)arr_get($d, 'email', ''));
        $address = trim((string)arr_get($d, 'address', ''));
        $postalCode = trim((string)arr_get($d, 'postal_code', ''));
        $active = (int)arr_get($d, 'active', 1);
        if ($businessName === '') jexit(['ok' => false, 'error' => 'Captura el nombre del cliente.'], 400);
        if ($locationId <= 0) $locationId = $currentLocationId;
        if (!$canSeeAllLocations && $locationId !== $currentLocationId) jexit(['ok' => false, 'error' => 'No tienes permiso para clientes de otra planta.'], 403);
        if (!in_array($active, [0,1], true)) $active = 1;
        if ($customerId > 0) {
            $sql = "UPDATE sales_customers SET business_name=?, contact_name=?, phone=?, email=?, address=?, postal_code=?, active=?, updated_at=NOW()";
            $params = [$businessName, ($contactName !== '' ? $contactName : null), ($phone !== '' ? $phone : null), ($email !== '' ? $email : null), ($address !== '' ? $address : null), ($postalCode !== '' ? $postalCode : null), $active];
            if (sales_column_exists($pdo, 'sales_customers', 'location_id')) { $sql .= ", location_id=?"; $params[] = $locationId; }
            $sql .= " WHERE id=? AND company_id=?";
            $params[] = $customerId; $params[] = $companyId;
            if (!$canSeeAllLocations && sales_column_exists($pdo, 'sales_customers', 'location_id')) { $sql .= " AND (location_id IS NULL OR location_id=?)"; $params[] = $currentLocationId; }
            $pdo->prepare($sql)->execute($params);
        } else {
            if (sales_column_exists($pdo, 'sales_customers', 'location_id')) {
                $pdo->prepare("INSERT INTO sales_customers (company_id, location_id, business_name, contact_name, phone, email, address, postal_code, active, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW())")->execute([$companyId, $locationId, $businessName, ($contactName !== '' ? $contactName : null), ($phone !== '' ? $phone : null), ($email !== '' ? $email : null), ($address !== '' ? $address : null), ($postalCode !== '' ? $postalCode : null), $active]);
            } else {
                $pdo->prepare("INSERT INTO sales_customers (company_id, business_name, contact_name, phone, email, address, postal_code, active, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW())")->execute([$companyId, $businessName, ($contactName !== '' ? $contactName : null), ($phone !== '' ? $phone : null), ($email !== '' ? $email : null), ($address !== '' ? $address : null), ($postalCode !== '' ? $postalCode : null), $active]);
            }
            $customerId = (int)$pdo->lastInsertId();
        }
        jexit(['ok' => true, 'customer_id' => $customerId]);
    }

    if ($action === 'customer_addresses') {
        if (!$canManage) jexit(['ok' => false, 'error' => 'Sin permiso'], 403);
        $customerId = (int)($_GET['customer_id'] ?? 0);
        if ($customerId <= 0 || !customer_belongs_to_company($pdo, $customerId, $companyId)) jexit(['ok' => false, 'error' => 'Cliente inválido'], 400);
        $st = $pdo->prepare("SELECT id, customer_id, label, address, postal_code, is_default, active, created_at FROM sales_customer_addresses WHERE customer_id = ? ORDER BY is_default DESC, active DESC, id DESC");
        $st->execute([$customerId]);
        jexit(['ok' => true, 'rows' => $st->fetchAll(PDO::FETCH_ASSOC)]);
    }

    if ($action === 'customer_address_save') {
        if (!$canManage) jexit(['ok' => false, 'error' => 'Sin permiso'], 403);
        $d = read_json(); $addressId = (int)arr_get($d, 'address_id', 0); $customerId = (int)arr_get($d, 'customer_id', 0);
        $label = trim((string)arr_get($d, 'label', '')); $address = trim((string)arr_get($d, 'address', '')); $postalCode = trim((string)arr_get($d, 'postal_code', ''));
        $isDefault = (int)arr_get($d, 'is_default', 0) === 1 ? 1 : 0; $active = (int)arr_get($d, 'active', 1) === 0 ? 0 : 1;
        if ($customerId <= 0 || !customer_belongs_to_company($pdo, $customerId, $companyId)) jexit(['ok' => false, 'error' => 'Cliente inválido'], 400);
        if ($address === '') jexit(['ok' => false, 'error' => 'Captura la dirección de entrega.'], 400);
        if ($isDefault) $pdo->prepare("UPDATE sales_customer_addresses SET is_default = 0 WHERE customer_id = ?")->execute([$customerId]);
        if ($addressId > 0) {
            $pdo->prepare("UPDATE sales_customer_addresses SET label=?, address=?, postal_code=?, is_default=?, active=? WHERE id=? AND customer_id=?")->execute([($label !== '' ? $label : null), $address, ($postalCode !== '' ? $postalCode : null), $isDefault, $active, $addressId, $customerId]);
        } else {
            $pdo->prepare("INSERT INTO sales_customer_addresses (customer_id, label, address, postal_code, is_default, active, created_at) VALUES (?, ?, ?, ?, ?, ?, NOW())")->execute([$customerId, ($label !== '' ? $label : null), $address, ($postalCode !== '' ? $postalCode : null), $isDefault, $active]);
            $addressId = (int)$pdo->lastInsertId();
        }
        jexit(['ok' => true, 'address_id' => $addressId]);
    }

    if ($action === 'customer_address_toggle') {
        if (!$canManage) jexit(['ok' => false, 'error' => 'Sin permiso'], 403);
        $d = read_json(); $addressId = (int)arr_get($d, 'address_id', 0); $active = (int)arr_get($d, 'active', -1);
        if ($addressId <= 0 || !in_array($active, [0,1], true)) jexit(['ok' => false, 'error' => 'Datos inválidos'], 400);
        $pdo->prepare("UPDATE sales_customer_addresses a JOIN sales_customers c ON c.id = a.customer_id SET a.active = ? WHERE a.id = ? AND c.company_id = ?")->execute([$active, $addressId, $companyId]);
        jexit(['ok' => true]);
    }

    if ($action === 'customer_toggle') {
        if (!$canManage) {
            jexit(['ok' => false, 'error' => 'Sin permiso'], 403);
        }

        $d = read_json();
        $customerId = (int)arr_get($d, 'customer_id', 0);
        $active     = (int)arr_get($d, 'active', -1);

        if ($customerId <= 0 || !in_array($active, [0, 1], true)) {
            jexit(['ok' => false, 'error' => 'Datos inválidos'], 400);
        }

        $st = $pdo->prepare("
            UPDATE sales_customers
            SET active = ?, updated_at = NOW()
            WHERE id = ? AND company_id = ?
        ");
        $st->execute([$active, $customerId, $companyId]);

        jexit(['ok' => true]);
    }

    if ($action === 'products_admin') {
        if (!$canManage) {
            jexit(['ok' => false, 'error' => 'Sin permiso'], 403);
        }

        $q = trim((string)($_GET['q'] ?? ''));

        $sql = "
            SELECT id, sku, name, uom, active
            FROM products
            WHERE company_id = ?
        ";
        $params = [$companyId];

        if ($q !== '') {
            $sql .= " AND (sku LIKE ? OR name LIKE ?)";
            $params[] = '%' . $q . '%';
            $params[] = '%' . $q . '%';
        }

        $sql .= " ORDER BY active DESC, sku ASC";

        $st = $pdo->prepare($sql);
        $st->execute($params);

        jexit([
            'ok' => true,
            'rows' => $st->fetchAll(PDO::FETCH_ASSOC),
        ]);
    }

    if ($action === 'product_toggle') {
        if (!$canManage) {
            jexit(['ok' => false, 'error' => 'Sin permiso'], 403);
        }

        $d = read_json();
        $productId = (int)arr_get($d, 'product_id', 0);
        $active    = (int)arr_get($d, 'active', -1);

        if ($productId <= 0 || !in_array($active, [0, 1], true)) {
            jexit(['ok' => false, 'error' => 'Datos inválidos'], 400);
        }

        $st = $pdo->prepare("
            UPDATE products
            SET active = ?
            WHERE id = ? AND company_id = ?
        ");
        $st->execute([$active, $productId, $companyId]);

        jexit(['ok' => true]);
    }


    if ($action === 'customer_users') {
        if (!$canManage) {
            jexit(['ok' => false, 'error' => 'Sin permiso'], 403);
        }

        $customerId = (int)($_GET['customer_id'] ?? 0);
        if ($customerId <= 0 || !customer_belongs_to_company($pdo, $customerId, $companyId)) {
            jexit(['ok' => false, 'error' => 'Cliente inválido'], 400);
        }

        $st = $pdo->prepare("
            SELECT cu.id, cu.customer_id, cu.full_name, cu.phone, cu.username, cu.role, cu.email, cu.active, cu.last_login_at, cu.created_at, cu.updated_at,
                   sc.business_name
            FROM sales_customer_users cu
            JOIN sales_customers sc ON sc.id = cu.customer_id
            WHERE cu.customer_id = ?
            ORDER BY cu.active DESC, cu.full_name ASC, cu.id DESC
        ");
        $st->execute([$customerId]);

        jexit([
            'ok' => true,
            'rows' => $st->fetchAll(PDO::FETCH_ASSOC),
        ]);
    }

    if ($action === 'customer_user_create') {
        if (!$canManage) {
            jexit(['ok' => false, 'error' => 'Sin permiso'], 403);
        }

        $d = read_json();
        $customerId = (int)arr_get($d, 'customer_id', 0);
        $fullName   = trim((string)arr_get($d, 'full_name', ''));
        $phone      = normalize_phone((string)arr_get($d, 'phone', ''));
        $password   = (string)arr_get($d, 'password', '');
        $email      = trim((string)arr_get($d, 'email', ''));
        $role       = trim((string)arr_get($d, 'role', 'cliente'));
        $active     = (int)arr_get($d, 'active', 1);

        if ($customerId <= 0 || !customer_belongs_to_company($pdo, $customerId, $companyId)) {
            jexit(['ok' => false, 'error' => 'Cliente inválido'], 400);
        }
        if ($fullName === '') {
            jexit(['ok' => false, 'error' => 'Captura el nombre del usuario'], 400);
        }
        if ($phone === '' || mb_strlen($phone) < 8) {
            jexit(['ok' => false, 'error' => 'Captura un teléfono válido'], 400);
        }
        if ($password === '' || mb_strlen($password) < 4) {
            jexit(['ok' => false, 'error' => 'La contraseña debe tener al menos 4 caracteres'], 400);
        }
        if (!in_array($role, ['cliente', 'tienda'], true)) {
            $role = 'cliente';
        }
        if (!in_array($active, [0, 1], true)) {
            $active = 1;
        }

        $st = $pdo->prepare("SELECT id FROM sales_customer_users WHERE phone = ? LIMIT 1");
        $st->execute([$phone]);
        if ($st->fetchColumn()) {
            jexit(['ok' => false, 'error' => 'Ya existe un usuario con ese teléfono'], 409);
        }

        $passHash = password_hash($password, PASSWORD_DEFAULT);
        $username = $phone;
        $emailVal = ($email === '' ? null : $email);

        $st = $pdo->prepare("
            INSERT INTO sales_customer_users (
                customer_id, role, username, full_name, phone, email, pass_hash, active, created_at, updated_at
            ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW())
        ");
        $st->execute([$customerId, $role, $username, $fullName, $phone, $emailVal, $passHash, $active]);

        jexit(['ok' => true]);
    }

    if ($action === 'customer_user_update') {
        if (!$canManage) {
            jexit(['ok' => false, 'error' => 'Sin permiso'], 403);
        }

        $d = read_json();
        $userId   = (int)arr_get($d, 'user_id', 0);
        $fullName = trim((string)arr_get($d, 'full_name', ''));
        $phone    = normalize_phone((string)arr_get($d, 'phone', ''));
        $email    = trim((string)arr_get($d, 'email', ''));
        $role     = trim((string)arr_get($d, 'role', 'cliente'));
        $active   = (int)arr_get($d, 'active', 1);

        if ($userId <= 0) {
            jexit(['ok' => false, 'error' => 'Usuario inválido'], 400);
        }
        if ($fullName === '') {
            jexit(['ok' => false, 'error' => 'Captura el nombre del usuario'], 400);
        }
        if ($phone === '' || mb_strlen($phone) < 8) {
            jexit(['ok' => false, 'error' => 'Captura un teléfono válido'], 400);
        }
        if (!in_array($role, ['cliente', 'tienda'], true)) {
            $role = 'cliente';
        }
        if (!in_array($active, [0, 1], true)) {
            $active = 1;
        }

        $st = $pdo->prepare("
            SELECT cu.id
            FROM sales_customer_users cu
            JOIN sales_customers sc ON sc.id = cu.customer_id
            WHERE cu.id = ? AND sc.company_id = ?
            LIMIT 1
        ");
        $st->execute([$userId, $companyId]);
        if (!$st->fetchColumn()) {
            jexit(['ok' => false, 'error' => 'Usuario no encontrado'], 404);
        }

        $st = $pdo->prepare("SELECT id FROM sales_customer_users WHERE phone = ? AND id <> ? LIMIT 1");
        $st->execute([$phone, $userId]);
        if ($st->fetchColumn()) {
            jexit(['ok' => false, 'error' => 'Ya existe otro usuario con ese teléfono'], 409);
        }

        $username = $phone;
        $emailVal = ($email === '' ? null : $email);

        $st = $pdo->prepare("
            UPDATE sales_customer_users
            SET full_name = ?, phone = ?, username = ?, email = ?, role = ?, active = ?, updated_at = NOW()
            WHERE id = ?
        ");
        $st->execute([$fullName, $phone, $username, $emailVal, $role, $active, $userId]);

        jexit(['ok' => true]);
    }

    if ($action === 'customer_user_password') {
        if (!$canManage) {
            jexit(['ok' => false, 'error' => 'Sin permiso'], 403);
        }

        $d = read_json();
        $userId   = (int)arr_get($d, 'user_id', 0);
        $password = (string)arr_get($d, 'password', '');

        if ($userId <= 0) {
            jexit(['ok' => false, 'error' => 'Usuario inválido'], 400);
        }
        if ($password === '' || mb_strlen($password) < 4) {
            jexit(['ok' => false, 'error' => 'La contraseña debe tener al menos 4 caracteres'], 400);
        }

        $st = $pdo->prepare("
            SELECT cu.id
            FROM sales_customer_users cu
            JOIN sales_customers sc ON sc.id = cu.customer_id
            WHERE cu.id = ? AND sc.company_id = ?
            LIMIT 1
        ");
        $st->execute([$userId, $companyId]);
        if (!$st->fetchColumn()) {
            jexit(['ok' => false, 'error' => 'Usuario no encontrado'], 404);
        }

        $passHash = password_hash($password, PASSWORD_DEFAULT);
        $st = $pdo->prepare("UPDATE sales_customer_users SET pass_hash = ?, updated_at = NOW() WHERE id = ?");
        $st->execute([$passHash, $userId]);

        jexit(['ok' => true]);
    }

    if ($action === 'customer_user_toggle') {
        if (!$canManage) {
            jexit(['ok' => false, 'error' => 'Sin permiso'], 403);
        }

        $d = read_json();
        $userId = (int)arr_get($d, 'user_id', 0);
        $active = (int)arr_get($d, 'active', -1);

        if ($userId <= 0 || !in_array($active, [0, 1], true)) {
            jexit(['ok' => false, 'error' => 'Datos inválidos'], 400);
        }

        $st = $pdo->prepare("
            UPDATE sales_customer_users cu
            JOIN sales_customers sc ON sc.id = cu.customer_id
            SET cu.active = ?, cu.updated_at = NOW()
            WHERE cu.id = ? AND sc.company_id = ?
        ");
        $st->execute([$active, $userId, $companyId]);

        jexit(['ok' => true]);
    }

    jexit(['ok' => false, 'error' => 'Acción inválida'], 404);
}

/* ============================================================
   UI
============================================================ */
$currentSalesContext = sales_location_context($pdo, $companyId, $currentLocationId);
$isHieloBarraScope = !empty($currentSalesContext['is_hielo_scope']);
$csrf = csrf_token();
?>
<!doctype html>
<html lang="es">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>BReport · Ventas · Pedidos</title>

  <link rel="preconnect" href="https://fonts.googleapis.com">
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
  <link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css" rel="stylesheet">

  <style>
    :root{
      --bg1:#031126;
      --bg2:#071d3b;
      --line:rgba(145,188,255,.14);
      --txt:#eef4ff;
      --txt-strong:#ffffff;
      --txt-soft:#c7d8f4;
      --gold:#f4be59;
      --green:#72f0a5;
      --danger:#ff8fa3;
      --info:#57a5ff;
      --shadow:0 20px 60px rgba(0,0,0,.35);
      --radius:24px;
    }

    *{box-sizing:border-box}
    html,body{min-height:100%}

    body{
      margin:0;
      font-family:'Inter',system-ui,Arial,sans-serif;
      color:var(--txt);
      background:
        radial-gradient(circle at 12% 20%, rgba(35,120,255,.20), transparent 26%),
        radial-gradient(circle at 88% 80%, rgba(0,190,255,.10), transparent 22%),
        linear-gradient(135deg, var(--bg1) 0%, #03152e 44%, var(--bg2) 100%);
      background-attachment:fixed;
    }

    .topbar{
      background:rgba(5,18,43,.76);
      backdrop-filter:blur(12px);
      border-bottom:1px solid rgba(255,255,255,.08);
      box-shadow:0 8px 24px rgba(0,0,0,.18);
      position:sticky;
      top:0;
      z-index:1040;
    }

    .page-wrap{padding:28px 18px 40px}
    .shell{max-width:1500px;margin:0 auto}
    .panel-pad{padding:22px}
    .brand-title{font-weight:800;font-size:1.9rem;color:var(--txt-strong)}
    .brand-sub,.muted,.small,.small-note,.form-label{color:var(--txt-soft)!important}

    .glass{
      background:linear-gradient(180deg, rgba(8,26,61,.94), rgba(4,18,44,.92));
      border:1px solid rgba(145,188,255,.14);
      border-radius:var(--radius);
      box-shadow:var(--shadow);
      backdrop-filter:blur(12px);
    }

    .section-title,.fw-bold,.fw-semibold,.table thead th{color:var(--txt-strong)}

    .toolbar-btn{
      border-radius:14px;
      border:1px solid rgba(160,199,255,.22);
      background:rgba(15,37,81,.74);
      color:#f7fbff!important;
      font-weight:600;
      transition:all .18s ease;
    }

    .toolbar-btn:hover{transform:translateY(-1px);color:#fff!important;background:rgba(28,53,104,.92)}
    .btn-primary-soft{
      color:#fff!important;font-weight:700;border:none;border-radius:18px;
      background:linear-gradient(135deg, #1e63ff, #3898ff);
      box-shadow:0 12px 32px rgba(37,113,255,.28)
    }
    .btn-green-soft{
      color:#fff!important;font-weight:700;border:none;border-radius:18px;
      background:linear-gradient(135deg, #149f5e, #24cd7a);
      box-shadow:0 12px 32px rgba(20,159,94,.24)
    }
    .btn-danger-soft{
      color:#fff!important;font-weight:700;border:none;border-radius:18px;
      background:linear-gradient(135deg, #c44b5f, #ef7388);
      box-shadow:0 12px 32px rgba(196,75,95,.24)
    }
    .btn-warning-soft{
      color:#fff!important;font-weight:700;border:none;border-radius:18px;
      background:linear-gradient(135deg, #b9851f, #f0bb4d);
      box-shadow:0 12px 32px rgba(185,133,31,.24)
    }

    .table-wrap{
      border:1px solid rgba(162,198,255,.12);
      border-radius:22px;
      overflow:hidden;
      background:rgba(5,20,47,.52);
    }

    .table{
      margin:0;
      color:var(--txt);
      --bs-table-bg:transparent;
      --bs-table-hover-bg:rgba(68,124,220,.10);
      --bs-table-border-color:rgba(169,203,255,.08);
    }
    .table thead th{
      color:#f8fbff!important;font-weight:800;font-size:.95rem;background:rgba(12,31,71,.82);
      white-space:nowrap;border-bottom:1px solid rgba(169,203,255,.12);padding-top:1rem;padding-bottom:1rem
    }
    .table tbody td{color:#eaf2ff!important;vertical-align:middle;border-color:rgba(169,203,255,.08)}

    .mono{
      font-family:ui-monospace,SFMono-Regular,Consolas,monospace;
      font-variant-numeric:tabular-nums;
      color:#f4f8ff!important;
      letter-spacing:.2px;
    }

    .form-control,.form-select{
      min-height:48px;
      border-radius:16px;
      color:#eef4ff!important;
      caret-color:#ffffff;
      background:rgba(8,24,58,.84)!important;
      border:1px solid rgba(160,197,255,.18);
      box-shadow:none!important;
      transition:border-color .18s ease, box-shadow .18s ease, background-color .18s ease;
    }
    .form-control::placeholder{color:#9fb4d8;opacity:1}
    .form-control:focus,.form-select:focus,
    .form-control:active,.form-select:active{
      color:#ffffff!important;
      background:rgba(10,30,70,.96)!important;
      border-color:rgba(118,176,255,.58)!important;
      box-shadow:0 0 0 .22rem rgba(64,132,255,.18)!important;
      outline:none;
    }
    .form-control:disabled,.form-select:disabled,
    .form-control[readonly]{
      color:#d3e2fb!important;
      background:rgba(18,34,66,.88)!important;
      border-color:rgba(160,197,255,.12)!important;
      opacity:1;
    }
    .form-select option,
    .form-select optgroup{
      color:#eef4ff;
      background:#0b1f45;
    }
    .form-control::-webkit-date-and-time-value{color:#eef4ff}
    .form-control::-webkit-calendar-picker-indicator{
      filter:invert(1) brightness(1.2);
      opacity:.9;
      cursor:pointer;
    }
    .form-control::selection,
    .form-select::selection{
      color:#ffffff;
      background:rgba(76,146,255,.45);
    }
    input:-webkit-autofill,
    input:-webkit-autofill:hover,
    input:-webkit-autofill:focus,
    textarea:-webkit-autofill,
    textarea:-webkit-autofill:hover,
    textarea:-webkit-autofill:focus,
    select:-webkit-autofill,
    select:-webkit-autofill:hover,
    select:-webkit-autofill:focus{
      -webkit-text-fill-color:#eef4ff!important;
      -webkit-box-shadow:0 0 0 1000px rgba(10,30,70,.96) inset!important;
      box-shadow:0 0 0 1000px rgba(10,30,70,.96) inset!important;
      transition:background-color 9999s ease-out 0s;
      caret-color:#ffffff;
    }

    .badge-status{
      display:inline-flex;align-items:center;padding:8px 12px;border-radius:999px;
      font-weight:800;font-size:.82rem;letter-spacing:.2px
    }

    .st-pendiente{background:rgba(255,255,255,.10);color:#eef4ff;border:1px solid rgba(255,255,255,.12)}
    .st-revision{background:rgba(87,165,255,.16);color:#8fc5ff;border:1px solid rgba(87,165,255,.24)}
    .st-autorizada{background:rgba(114,240,165,.16);color:#7cf0ae;border:1px solid rgba(114,240,165,.24)}
    .st-rechazada{background:rgba(255,143,163,.16);color:#ff9caf;border:1px solid rgba(255,143,163,.24)}
    .st-cancelada{background:rgba(255,143,163,.10);color:#f0a7b5;border:1px solid rgba(255,143,163,.18)}
    .st-surtiendo{background:rgba(244,190,89,.16);color:#ffd978;border:1px solid rgba(244,190,89,.24)}
    .st-en_ruta{background:rgba(120,205,255,.16);color:#9bd6ff;border:1px solid rgba(120,205,255,.24)}
    .st-entregada{background:rgba(83,224,158,.16);color:#85efba;border:1px solid rgba(83,224,158,.24)}

    .metric-card{
      border-radius:22px;border:1px solid rgba(160,198,255,.16);
      background:linear-gradient(180deg, rgba(8,25,60,.96), rgba(4,18,42,.94));
      padding:18px;min-height:128px
    }
    .metric-label{color:#b8ccef;font-weight:700}
    .metric-value{color:#fff;font-size:2rem;font-weight:800}

    .modal-content{
      background:linear-gradient(180deg, rgba(7,24,58,.98), rgba(4,18,44,.98));
      border:1px solid rgba(161,198,255,.16);color:#fff;border-radius:24px
    }
    .modal-header,.modal-footer{border-color:rgba(161,198,255,.12)}

    .log-item{
      border:1px solid rgba(160,197,255,.10);background:rgba(255,255,255,.035);
      border-radius:16px;padding:12px 14px
    }

    .stock-card{
      border:1px solid rgba(160,197,255,.10);background:rgba(255,255,255,.035);
      border-radius:18px;padding:14px;height:100%
    }
    .stock-title{color:#fff;font-weight:700}
    .stock-sub{color:#abc2e6;font-size:.88rem}
    .stock-value{font-size:1.6rem;font-weight:800;color:#7cf0ae}

    .pill-active,.pill-inactive{
      display:inline-flex;align-items:center;padding:6px 10px;border-radius:999px;font-size:.8rem;font-weight:800
    }
    .pill-active{background:rgba(114,240,165,.16);color:#7cf0ae;border:1px solid rgba(114,240,165,.24)}
    .pill-inactive{background:rgba(255,143,163,.16);color:#ff9caf;border:1px solid rgba(255,143,163,.24)}

    @media (max-width: 991.98px){
      .brand-title{font-size:1.45rem}
      .page-wrap{padding:18px 12px 28px}
      .panel-pad{padding:16px}
      .metric-value{font-size:1.5rem}
    }


    /* ============================================================
       TEMA CLARO · Ventas
       ============================================================ */
    :root{--bg1:#f4f7fb;--bg2:#eef3f9;--line:#d8e2ef;--txt:#1f2937;--txt-strong:#0f172a;--txt-soft:#64748b;--gold:#d99a20;--green:#138a52;--danger:#c24155;--info:#2563eb;--shadow:0 18px 42px rgba(15,23,42,.10);--radius:22px;}
    body{color:var(--txt);background:radial-gradient(circle at 12% 18%, rgba(37,99,235,.10), transparent 24%),radial-gradient(circle at 88% 78%, rgba(14,165,233,.10), transparent 22%),linear-gradient(135deg, #f8fafc 0%, #eef4fb 100%);background-attachment:fixed;}
    .topbar{background:rgba(255,255,255,.92)!important;border-bottom:1px solid #e2e8f0!important;box-shadow:0 10px 28px rgba(15,23,42,.08)!important;}
    .brand-title{color:#0f172a!important}.brand-sub,.muted,.small,.small-note,.form-label{color:#64748b!important}
    .glass,.metric-card,.log-item,.stock-card{background:#ffffff!important;border:1px solid #dbe5f1!important;box-shadow:0 16px 36px rgba(15,23,42,.08)!important;color:#1f2937!important;}
    .section-title,.fw-bold,.fw-semibold,.stock-title{color:#0f172a!important}
    .toolbar-btn{background:#ffffff!important;border:1px solid #cbd5e1!important;color:#1e293b!important;box-shadow:0 8px 20px rgba(15,23,42,.06);}
    .toolbar-btn:hover{background:#f1f5f9!important;color:#0f172a!important}.btn-primary-soft{background:linear-gradient(135deg,#2563eb,#0ea5e9)!important;color:#fff!important;box-shadow:0 12px 24px rgba(37,99,235,.22)!important}.btn-green-soft{background:linear-gradient(135deg,#138a52,#22c55e)!important;color:#fff!important;box-shadow:0 12px 24px rgba(20,150,90,.20)!important}.btn-danger-soft{background:linear-gradient(135deg,#be123c,#fb7185)!important;color:#fff!important;box-shadow:0 12px 24px rgba(190,18,60,.18)!important}.btn-warning-soft{background:linear-gradient(135deg,#d97706,#fbbf24)!important;color:#fff!important;box-shadow:0 12px 24px rgba(217,119,6,.20)!important}
    .table-wrap{background:#ffffff!important;border:1px solid #dbe5f1!important;border-radius:20px!important}.table{--bs-table-bg:#ffffff;--bs-table-hover-bg:#f8fafc;--bs-table-border-color:#e2e8f0;color:#1f2937!important}.table thead th{background:#eff6ff!important;color:#0f172a!important;border-bottom:1px solid #dbe5f1!important}.table tbody td{color:#1f2937!important;border-color:#e2e8f0!important}.mono{color:#0f172a!important}
    .form-control,.form-select{color:#0f172a!important;background:#ffffff!important;border:1px solid #cbd5e1!important;box-shadow:none!important}.form-control::placeholder{color:#94a3b8!important}.form-control:focus,.form-select:focus{color:#0f172a!important;background:#ffffff!important;border-color:#3b82f6!important;box-shadow:0 0 0 .22rem rgba(59,130,246,.16)!important}.form-select option,.form-select optgroup{color:#0f172a;background:#ffffff}.form-control::-webkit-calendar-picker-indicator{filter:none!important}
    .modal-content{background:#ffffff!important;color:#1f2937!important;border:1px solid #dbe5f1!important;box-shadow:0 24px 64px rgba(15,23,42,.18)!important}.modal-header,.modal-footer{border-color:#e2e8f0!important}.btn-close-white{filter:none!important}
    .metric-label{color:#64748b!important}.metric-value{color:#0f172a!important}.stock-sub{color:#64748b!important}.stock-value{color:#138a52!important}.badge-status{border-width:1px;background:#f8fafc;color:#334155}
    .st-pendiente{background:#f1f5f9!important;color:#334155!important;border-color:#cbd5e1!important}.st-revision{background:#dbeafe!important;color:#1d4ed8!important;border-color:#93c5fd!important}.st-autorizada{background:#dcfce7!important;color:#15803d!important;border-color:#86efac!important}.st-rechazada,.st-cancelada{background:#ffe4e6!important;color:#be123c!important;border-color:#fecdd3!important}.st-surtiendo{background:#fef3c7!important;color:#b45309!important;border-color:#fde68a!important}.st-en_ruta{background:#e0f2fe!important;color:#0369a1!important;border-color:#bae6fd!important}.st-entregada{background:#d1fae5!important;color:#047857!important;border-color:#a7f3d0!important}.pill-active{background:#dcfce7!important;color:#15803d!important;border-color:#86efac!important}.pill-inactive{background:#ffe4e6!important;color:#be123c!important;border-color:#fecdd3!important}
    .view-tabs .btn{border-radius:999px;font-weight:800;padding:.65rem 1rem}.view-tabs .btn.active{background:#0f172a!important;color:#ffffff!important;border-color:#0f172a!important}.alert-soft{background:#f8fafc;border:1px solid #dbe5f1;border-radius:18px;padding:14px;color:#334155}

  
    /* =========================================================
       Bandeja comercial MES
    ========================================================= */
    .sales-ai{
      background:#fff;
      border:1px solid #dbe6f2;
      border-radius:22px;
      padding:18px;
      box-shadow:0 14px 34px rgba(16,36,62,.08);
      color:#10243e;
    }
    .ai-kpis{
      display:grid;
      grid-template-columns:repeat(4,minmax(130px,1fr));
      gap:12px;
      margin-top:14px;
    }
    .ai-kpi{
      border:1px solid #e1ebf5;
      border-radius:16px;
      background:#f8fbff;
      padding:12px 14px;
    }
    .ai-kpi-label{font-size:.75rem;color:#64748b;text-transform:uppercase;font-weight:800;}
    .ai-kpi-value{font-size:1.6rem;color:#10243e;font-weight:900;line-height:1.15;margin-top:5px;}
    .ai-body{
      display:grid;
      grid-template-columns:minmax(280px,1.1fr) minmax(260px,.9fr) minmax(260px,.9fr);
      gap:12px;
      margin-top:14px;
    }
    .ai-box{border:1px solid #e1ebf5;border-radius:16px;padding:13px;background:#fff;min-height:150px;}
    .ai-box-title{font-weight:900;color:#10243e;margin-bottom:10px;}
    .ai-alert{padding:10px;border-radius:12px;margin-bottom:8px;border:1px solid transparent;color:#334155;font-size:.84rem;}
    .ai-alert:last-child{margin-bottom:0;}
    .ai-alert.info{background:#eff6ff;border-color:#bfdbfe;}
    .ai-alert.warning{background:#fff7e6;border-color:#f3d39b;}
    .ai-alert.critical{background:#fff1f3;border-color:#f0b5c0;}
    .ai-alert.success{background:#ecfdf3;border-color:#a6e7c0;}
    .ai-alert-title{font-weight:900;color:#10243e;}
    .ai-row{display:flex;justify-content:space-between;gap:10px;padding:8px 0;border-bottom:1px solid #eef3f8;font-size:.83rem;}
    .ai-row:last-child{border-bottom:0;}
    .ai-row-title{font-weight:800;color:#19324d;}
    .ai-row-sub{font-size:.76rem;color:#64748b;}
    .ai-tag{padding:4px 8px;border-radius:999px;background:#eef6ff;color:#1153c7;font-weight:800;white-space:nowrap;}
    .ai-tag.warning{background:#fff3d6;color:#8a5b04;}
    .ai-tag.critical{background:#fee2e2;color:#b91c1c;}

    .mailbox{
      display:grid;
      grid-template-columns:230px minmax(320px,420px) minmax(360px,1fr);
      min-height:600px;
      border:1px solid #dbe6f2;
      border-radius:22px;
      overflow:hidden;
      background:#fff;
      box-shadow:0 14px 34px rgba(16,36,62,.08);
    }
    .mailbox-folders{padding:16px 12px;background:#f5f9fe;border-right:1px solid #dbe6f2;}
    .mail-folder{
      display:flex;align-items:center;justify-content:space-between;gap:8px;
      width:100%;border:0;border-radius:14px;padding:12px 10px;margin-bottom:7px;
      background:transparent;color:#19324d;font-weight:800;text-align:left;
    }
    .mail-folder:hover,.mail-folder.active{background:#e8f1ff;color:#1153c7;}
    .mail-count{
      display:inline-flex;align-items:center;justify-content:center;min-width:28px;height:25px;
      border-radius:999px;padding:0 8px;background:#e3ebf6;font-size:.82rem;color:#19324d;
    }
    .mail-folder.active .mail-count{background:#2563eb;color:#fff;}
    .mail-scope-note{
      margin-top:18px;border:1px solid #c9dcf6;border-radius:13px;
      padding:11px;background:#eef6ff;color:#425b76;font-size:.82rem;
    }
    .mail-list{border-right:1px solid #dbe6f2;min-width:0;background:#fff;}
    .mail-list-title{padding:16px;border-bottom:1px solid #e6edf5;}
    .mail-items{max-height:535px;overflow:auto;}
    .mail-item{
      width:100%;padding:15px 16px;border:0;border-bottom:1px solid #eef3f8;
      text-align:left;background:#fff;color:#10243e;
    }
    .mail-item:hover,.mail-item.active{background:#f1f7ff;}
    .mail-item.active{box-shadow:inset 3px 0 0 #2563eb;}
    .mail-folio{font-weight:900;color:#122b48;}
    .mail-sub{font-size:.84rem;color:#64748b;margin-top:4px;}
    .mail-bottom{display:flex;justify-content:space-between;gap:8px;margin-top:8px;font-size:.8rem;color:#586e88;}
    .mail-detail{padding:20px;background:#fff;min-width:0;}
    .mail-empty{min-height:370px;display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;color:#64748b;padding:28px;}
    .mail-detail-card{border:1px solid #e1ebf5;background:#f8fbff;border-radius:14px;padding:12px;}
    .mail-detail-label{font-size:.71rem;color:#64748b;font-weight:800;text-transform:uppercase;}
    .mail-detail-value{font-weight:800;color:#10243e;margin-top:4px;}
    .mail-status{display:inline-flex;padding:5px 10px;border-radius:999px;font-size:.73rem;font-weight:900;}
    .mail-status.pending{background:#fff3d6;color:#8a5b04;}
    .mail-status.review{background:#e8f1ff;color:#1153c7;}
    .mail-status.authorized{background:#def8e9;color:#087443;}
    .mail-status.shipping{background:#ede9fe;color:#6d28d9;}
    .mail-status.done{background:#d1fae5;color:#065f46;}
    .mail-status.cancelled{background:#fee2e2;color:#b91c1c;}

    @media (max-width:1199.98px){
      .ai-kpis{grid-template-columns:repeat(2,minmax(150px,1fr));}
      .ai-body{grid-template-columns:1fr;}
      .mailbox{grid-template-columns:215px 1fr;}
      .mail-detail{grid-column:1/-1;border-top:1px solid #dbe6f2;}
    }
    @media (max-width:767.98px){
      .ai-kpis{grid-template-columns:1fr;}
      .mailbox{display:block;min-height:0;}
      .mailbox-folders{display:flex;gap:7px;flex-wrap:wrap;border-right:0;border-bottom:1px solid #dbe6f2;}
      .mail-folder{width:auto;margin-bottom:0;}
      .mail-scope-note{width:100%;margin-top:8px;}
      .mail-list{border-right:0;}
      .mail-items{max-height:390px;}
    }

  </style>
</head>
<body>

<nav class="navbar topbar navbar-expand-lg">
  <div class="container-fluid px-3 px-lg-4">
    <div>
      <div class="brand-title">BReport · Ventas · Pedidos</div>
      <div class="brand-sub">Revisión, autorización y control comercial de solicitudes</div>
    </div>

    <div class="ms-auto d-flex align-items-center gap-2 flex-wrap">
      <span class="muted small"><?= h($u['full_name'] ?? '') ?></span>
      <a class="btn toolbar-btn btn-sm" href="dashboard.php">Dashboard</a>
      <a class="btn toolbar-btn btn-sm" href="select_scope.php">Cambiar de empresa</a>
      <a class="btn toolbar-btn btn-sm" href="logout.php">Salir</a>
    </div>
  </div>
</nav>

<div class="page-wrap">
  <div class="shell">

    <div class="glass panel-pad mb-4">
      <div class="d-flex flex-wrap gap-3 align-items-end justify-content-between">
        <div>
          <div class="section-title mb-2">
            <i class="bi bi-bag-check-fill text-warning"></i>
            Centro comercial de pedidos
          </div>
          <div class="muted">
            Bandeja de seguimiento por planta y localidad. El inventario disponible corresponde únicamente a almacenes surtibles de la planta seleccionada.
          </div>
        </div>

        <div class="d-flex gap-2 flex-wrap">
          <button id="btnNewWarehouseOrder" class="btn btn-green-soft" type="button">
            <i class="bi bi-plus-circle me-1"></i>Nueva orden
          </button>
          <button id="btnOpenInventory" class="btn btn-warning-soft" type="button">
            <i class="bi bi-box-seam me-1"></i>Existencias
          </button>
          <button id="btnOpenCustomers" class="btn toolbar-btn" type="button">
            <i class="bi bi-people me-1"></i>Clientes
          </button>
          <button id="btnOpenProducts" class="btn toolbar-btn" type="button">
            <i class="bi bi-boxes me-1"></i>Artículos
          </button>
          <button id="btnReload" class="btn toolbar-btn" type="button">
            <i class="bi bi-arrow-repeat me-1"></i>Actualizar
          </button>
        </div>
      </div>

      <div class="row g-3 mt-3">
        <div class="col-12 col-md-3">
          <label class="form-label">Fecha desde</label>
          <input id="fDateFrom" type="date" class="form-control">
        </div>
        <div class="col-12 col-md-3">
          <label class="form-label">Fecha hasta</label>
          <input id="fDateTo" type="date" class="form-control">
        </div>
        <div class="col-12 col-md-3">
          <label class="form-label">Cliente / folio</label>
          <input id="fCustomer" class="form-control" placeholder="Buscar cliente o pedido">
        </div>
        <div class="col-12 col-md-3">
          <label class="form-label">Planta / ubicación</label>
          <select id="fLocation" class="form-select"></select>
        </div>
      </div>
      <input id="fStatus" type="hidden" value="">
    </div>

    <div class="sales-ai mb-4">
      <div class="d-flex align-items-start justify-content-between gap-3 flex-wrap">
        <div>
          <div class="section-title mb-1"><i class="bi bi-stars text-warning"></i> Diagnóstico inteligente de ventas</div>
          <div class="small muted">
            Pedidos, disponibilidad surtible y clientes activos de la planta seleccionada.
            <span class="mono" id="aiRange"></span>
          </div>
        </div>
        <button class="btn toolbar-btn btn-sm" id="btnReloadInsights" type="button">
          <i class="bi bi-arrow-repeat me-1"></i>Actualizar análisis
        </button>
      </div>
      <div class="ai-kpis">
        <div class="ai-kpi">
          <div class="ai-kpi-label">Pedidos del periodo</div>
          <div class="ai-kpi-value mono" id="aiOrders">0</div>
        </div>
        <div class="ai-kpi">
          <div class="ai-kpi-label">Por surtir</div>
          <div class="ai-kpi-value mono" id="aiDispatch">0</div>
        </div>
        <div class="ai-kpi">
          <div class="ai-kpi-label">Por revisar</div>
          <div class="ai-kpi-value mono" id="aiReview">0</div>
        </div>
        <div class="ai-kpi">
          <div class="ai-kpi-label">Riesgos de inventario</div>
          <div class="ai-kpi-value mono" id="aiRisks">0</div>
        </div>
      </div>
      <div class="ai-body">
        <div class="ai-box">
          <div class="ai-box-title"><i class="bi bi-lightbulb me-1"></i>Sugerencias comerciales</div>
          <div id="aiSuggestions"><div class="small muted">Analizando pedidos...</div></div>
        </div>
        <div class="ai-box">
          <div class="ai-box-title"><i class="bi bi-exclamation-triangle me-1"></i>Riesgo de surtido</div>
          <div id="aiRiskRows"><div class="small muted">Sin datos todavía.</div></div>
        </div>
        <div class="ai-box">
          <div class="ai-box-title"><i class="bi bi-people me-1"></i>Clientes con actividad</div>
          <div id="aiCustomers"><div class="small muted">Sin datos todavía.</div></div>
        </div>
      </div>
    </div>

    <div class="mailbox">
      <aside class="mailbox-folders">
        <button type="button" class="mail-folder active" data-mail-folder="all">
          <span><i class="bi bi-inboxes me-2"></i>Todos</span><span class="mail-count" id="mailCountAll">0</span>
        </button>
        <button type="button" class="mail-folder" data-mail-folder="review">
          <span><i class="bi bi-search me-2"></i>Por revisar</span><span class="mail-count" id="mailCountReview">0</span>
        </button>
        <button type="button" class="mail-folder" data-mail-folder="authorized">
          <span><i class="bi bi-check-circle me-2"></i>Autorizadas</span><span class="mail-count" id="mailCountAuthorized">0</span>
        </button>
        <button type="button" class="mail-folder" data-mail-folder="shipping">
          <span><i class="bi bi-truck me-2"></i>En proceso</span><span class="mail-count" id="mailCountShipping">0</span>
        </button>
        <button type="button" class="mail-folder" data-mail-folder="done">
          <span><i class="bi bi-check2-all me-2"></i>Entregadas</span><span class="mail-count" id="mailCountDone">0</span>
        </button>
        <button type="button" class="mail-folder" data-mail-folder="cancelled">
          <span><i class="bi bi-x-circle me-2"></i>Canceladas</span><span class="mail-count" id="mailCountCancelled">0</span>
        </button>
        <div class="mail-scope-note" id="mailInventoryNote">
          Inventario por almacenes surtibles de la planta activa.
        </div>
      </aside>

      <section class="mail-list">
        <div class="mail-list-title">
          <div class="fw-bold" id="mailFolderTitle">Todos los pedidos</div>
          <div class="small muted" id="mailFolderSub">0 registros</div>
        </div>
        <div class="mail-items" id="mailItems"></div>
      </section>

      <section class="mail-detail" id="mailDetail">
        <div class="mail-empty">
          <i class="bi bi-envelope-open fs-1 mb-2"></i>
          <div class="fw-bold">Selecciona un pedido</div>
          <div class="small">Consulta sus artículos, estatus y acciones comerciales.</div>
        </div>
      </section>
    </div>

    <div class="d-none">
      <button id="tabActive" type="button"></button>
      <button id="tabHistory" type="button"></button>
      <span id="kTotal"></span><span id="kPending"></span><span id="kApproved"></span>
      <div id="emptyHint"></div><table><tbody id="tbOrders"></tbody></table>
    </div>

  </div>
</div>

<!-- Modal detalle -->
<div class="modal fade" id="detailModal" tabindex="-1" aria-hidden="true">
  <div class="modal-dialog modal-xl modal-dialog-scrollable modal-dialog-centered">
    <div class="modal-content">
      <div class="modal-header">
        <div>
          <div class="fw-bold fs-5">Detalle de pedido</div>
          <div class="muted small">Consulta completa del pedido y acciones de ventas.</div>
        </div>
        <button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
      </div>

      <div class="modal-body">
        <div class="row g-3 mb-4" id="detailHeader"></div>

        <div class="glass panel-pad mb-4">
          <div class="section-title mb-3">
            <i class="bi bi-box-seam text-warning"></i>
            Artículos del pedido
          </div>

          <div class="table-wrap">
            <div class="table-responsive">
              <table class="table table-hover align-middle">
                <thead>
                  <tr>
                    <th>SKU</th>
                    <th>Producto</th>
                    <th class="text-end">Cantidad</th>
                    <th>UOM</th>
                    <th class="text-end">P. Unitario</th>
                    <th class="text-end">Importe</th>
                  </tr>
                </thead>
                <tbody id="detailItems"></tbody>
              </table>
            </div>
          </div>
        </div>

        <div class="glass panel-pad mb-4">
          <div class="section-title mb-3">
            <i class="bi bi-clock-history text-warning"></i>
            Bitácora de estatus
          </div>
          <div class="d-grid gap-2" id="detailLogs"></div>
        </div>

        <div class="glass panel-pad">
          <div class="section-title mb-3">
            <i class="bi bi-pencil-square text-warning"></i>
            Acción de ventas
          </div>

          <div class="row g-3">
            <div class="col-12 col-md-4">
              <label class="form-label">Nuevo estatus</label>
              <select id="mNewStatus" class="form-select">
                <option value="">Selecciona</option>
                <option value="EN_REVISION">Enviar a revisión</option>
                <option value="AUTORIZADA">Autorizar</option>
                <option value="RECHAZADA">Rechazar</option>
                <option value="CANCELADA">Cancelar</option>
              </select>
            </div>
            <div class="col-12 col-md-8">
              <label class="form-label">Comentario</label>
              <input id="mComment" class="form-control" placeholder="Motivo o comentario de seguimiento">
            </div>
          </div>
        </div>
      </div>

      <div class="modal-footer">
        <input type="hidden" id="mOrderId">
        <button type="button" class="btn toolbar-btn" data-bs-dismiss="modal">Cerrar</button>
        <button type="button" id="btnSaveStatus" class="btn btn-primary-soft">Guardar cambio</button>
      </div>
    </div>
  </div>
</div>

<!-- Modal existencias -->
<div class="modal fade" id="inventoryModal" tabindex="-1" aria-hidden="true">
  <div class="modal-dialog modal-xl modal-dialog-centered modal-dialog-scrollable">
    <div class="modal-content">
      <div class="modal-header">
        <div>
          <div class="fw-bold fs-5">Existencias disponibles</div>
          <div class="muted small">Consulta de inventario comercial por ubicación.</div>
        </div>
        <button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
      </div>

      <div class="modal-body">
        <div class="row g-3 mb-4">
          <div class="col-12 col-md-5">
            <label class="form-label">Ubicación</label>
            <select id="invLocationId" class="form-select"></select>
          </div>
          <div class="col-12 col-md-3">
            <label class="form-label">Acción</label>
            <button id="btnLoadInventory" type="button" class="btn btn-warning-soft w-100">
              <i class="bi bi-search me-1"></i>Consultar
            </button>
          </div>
          <div class="col-12 col-md-4 d-flex align-items-end">
            <div class="small-note w-100" id="invLocationInfo">
              Selecciona una ubicación para consultar existencias.
            </div>
          </div>
        </div>

        <div class="row g-3 mb-4" id="invCardsRow"></div>

        <div class="glass panel-pad">
          <div class="section-title mb-3">
            <i class="bi bi-box-seam text-warning"></i>
            Listado de artículos disponibles
          </div>

          <div class="table-wrap">
            <div class="table-responsive">
              <table class="table table-hover align-middle">
                <thead>
                  <tr>
                    <th>SKU</th>
                    <th>Producto</th>
                    <th class="text-end">Disponible</th>
                    <th>UOM</th>
                    <th>Estatus</th>
                  </tr>
                </thead>
                <tbody id="invStockBody"></tbody>
              </table>
            </div>
          </div>

          <div class="muted small mt-3" id="invEmptyHint"></div>
        </div>
      </div>

      <div class="modal-footer">
        <button type="button" class="btn toolbar-btn" data-bs-dismiss="modal">Cerrar</button>
      </div>
    </div>
  </div>
</div>

<!-- Modal clientes -->
<div class="modal fade" id="customersModal" tabindex="-1" aria-hidden="true">
  <div class="modal-dialog modal-xl modal-dialog-centered modal-dialog-scrollable">
    <div class="modal-content">
      <div class="modal-header">
        <div>
          <div class="fw-bold fs-5">Clientes comerciales</div>
          <div class="muted small">Activar o desactivar clientes disponibles para operación comercial.</div>
        </div>
        <button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
      </div>
      <div class="modal-body">
        <div class="d-flex justify-content-between align-items-end gap-2 flex-wrap mb-3">
          <div class="row g-3 flex-grow-1">
            <div class="col-12 col-md-8">
            <label class="form-label">Buscar cliente</label>
            <input id="customerSearch" class="form-control" placeholder="Nombre comercial, contacto o teléfono">
            </div>
          </div>
          <button id="btnNewCustomer" class="btn btn-primary-soft" type="button"><i class="bi bi-person-plus me-1"></i>Nuevo cliente</button>
        </div>

        <div class="table-wrap">
          <div class="table-responsive">
            <table class="table table-hover align-middle">
              <thead>
                <tr>
                  <th>Cliente</th>
                  <th>Contacto</th>
                  <th>Teléfono</th>
                  <th>Estatus</th>
                  <th class="text-end">Accesos</th>
                  <th class="text-end">Acción</th>
                </tr>
              </thead>
              <tbody id="tbCustomers"></tbody>
            </table>
          </div>
        </div>
        <div class="muted small mt-3" id="customersEmptyHint"></div>
      </div>
      <div class="modal-footer">
        <button type="button" class="btn toolbar-btn" data-bs-dismiss="modal">Cerrar</button>
      </div>
    </div>
  </div>
</div>


<!-- Modal alta / edición cliente -->
<div class="modal fade" id="customerEditModal" tabindex="-1" aria-hidden="true">
  <div class="modal-dialog modal-lg modal-dialog-centered modal-dialog-scrollable"><div class="modal-content">
    <div class="modal-header"><div><div class="fw-bold fs-5" id="customerEditTitle">Cliente</div><div class="muted small">Clientes por empresa y planta/localidad.</div></div><button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button></div>
    <div class="modal-body"><input type="hidden" id="ceCustomerId"><div class="row g-3">
      <div class="col-12 col-md-6"><label class="form-label">Planta / ubicación</label><select id="ceLocationId" class="form-select"></select></div>
      <div class="col-12 col-md-6"><label class="form-label">Estatus</label><select id="ceActive" class="form-select"><option value="1">Activo</option><option value="0">Inactivo</option></select></div>
      <div class="col-12 col-md-6"><label class="form-label">Nombre comercial</label><input id="ceBusinessName" class="form-control"></div>
      <div class="col-12 col-md-6"><label class="form-label">Contacto</label><input id="ceContactName" class="form-control"></div>
      <div class="col-12 col-md-4"><label class="form-label">Teléfono</label><input id="cePhone" class="form-control"></div>
      <div class="col-12 col-md-4"><label class="form-label">Correo</label><input id="ceEmail" class="form-control"></div>
      <div class="col-12 col-md-4"><label class="form-label">C.P.</label><input id="cePostalCode" class="form-control"></div>
      <div class="col-12"><label class="form-label">Dirección principal</label><input id="ceAddress" class="form-control"></div>
    </div><div id="customerEditResult" class="mt-3"></div></div>
    <div class="modal-footer"><button type="button" class="btn toolbar-btn" data-bs-dismiss="modal">Cerrar</button><button type="button" id="btnCustomerSave" class="btn btn-green-soft"><i class="bi bi-save me-1"></i>Guardar cliente</button></div>
  </div></div>
</div>

<!-- Modal direcciones cliente -->
<div class="modal fade" id="customerAddressesModal" tabindex="-1" aria-hidden="true">
  <div class="modal-dialog modal-xl modal-dialog-centered modal-dialog-scrollable"><div class="modal-content">
    <div class="modal-header"><div><div class="fw-bold fs-5">Direcciones de entrega</div><div class="muted small" id="caTitleSub">Registra diferentes destinos para el cliente.</div></div><button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button></div>
    <div class="modal-body"><input type="hidden" id="caCustomerId"><input type="hidden" id="caAddressId">
      <div class="glass panel-pad mb-4"><div class="row g-3">
        <div class="col-12 col-md-4"><label class="form-label">Etiqueta</label><input id="caLabel" class="form-control"></div>
        <div class="col-12 col-md-2"><label class="form-label">C.P.</label><input id="caPostalCode" class="form-control"></div>
        <div class="col-12 col-md-3"><label class="form-label">Predeterminada</label><select id="caDefault" class="form-select"><option value="0">No</option><option value="1">Sí</option></select></div>
        <div class="col-12 col-md-3"><label class="form-label">Estatus</label><select id="caActive" class="form-select"><option value="1">Activo</option><option value="0">Inactivo</option></select></div>
        <div class="col-12"><label class="form-label">Dirección</label><input id="caAddress" class="form-control"></div>
        <div class="col-12 d-flex gap-2"><button id="btnCustomerAddressSave" type="button" class="btn btn-green-soft"><i class="bi bi-save me-1"></i>Guardar dirección</button><button id="btnCustomerAddressClear" type="button" class="btn toolbar-btn">Limpiar</button></div>
      </div></div>
      <div class="table-wrap"><div class="table-responsive"><table class="table table-hover align-middle"><thead><tr><th>Etiqueta</th><th>Dirección</th><th>C.P.</th><th>Default</th><th>Estatus</th><th class="text-end">Acciones</th></tr></thead><tbody id="tbCustomerAddresses"></tbody></table></div></div><div class="muted small mt-3" id="customerAddressesEmptyHint"></div>
    </div><div class="modal-footer"><button type="button" class="btn toolbar-btn" data-bs-dismiss="modal">Cerrar</button></div>
  </div></div>
</div>

<!-- Modal accesos cliente -->
<div class="modal fade" id="customerUsersModal" tabindex="-1" aria-hidden="true">
  <div class="modal-dialog modal-xl modal-dialog-centered modal-dialog-scrollable">
    <div class="modal-content">
      <div class="modal-header">
        <div>
          <div class="fw-bold fs-5">Accesos del cliente</div>
          <div class="muted small" id="customerUsersTitleSub">Alta, edición, contraseña y activación del acceso del cliente.</div>
        </div>
        <button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
      </div>
      <div class="modal-body">
        <input type="hidden" id="cuCustomerId">
        <input type="hidden" id="cuEditingUserId">

        <div class="glass panel-pad mb-4">
          <div class="section-title mb-3"><i class="bi bi-person-plus-fill text-warning"></i> Usuario cliente</div>
          <div class="row g-3">
            <div class="col-12 col-md-4">
              <label class="form-label">Nombre</label>
              <input id="cuFullName" class="form-control" placeholder="Nombre del usuario cliente">
            </div>
            <div class="col-12 col-md-3">
              <label class="form-label">Teléfono / usuario</label>
              <input id="cuPhone" class="form-control" placeholder="Ej. 8991234567">
            </div>
            <div class="col-12 col-md-3">
              <label class="form-label">Correo</label>
              <input id="cuEmail" class="form-control" placeholder="Opcional">
            </div>
            <div class="col-12 col-md-2">
              <label class="form-label">Rol</label>
              <select id="cuRole" class="form-select">
                <option value="cliente">Cliente</option>
                <option value="tienda">Tienda</option>
              </select>
            </div>
            <div class="col-12 col-md-3">
              <label class="form-label">Contraseña</label>
              <input id="cuPassword" type="text" class="form-control" placeholder="Mínimo 4 caracteres">
            </div>
            <div class="col-12 col-md-3">
              <label class="form-label">Estatus</label>
              <select id="cuActive" class="form-select">
                <option value="1">Activo</option>
                <option value="0">Inactivo</option>
              </select>
            </div>
            <div class="col-12 col-md-6 d-flex align-items-end gap-2">
              <button id="btnCustomerUserSave" type="button" class="btn btn-primary-soft">
                <i class="bi bi-save me-1"></i>Guardar usuario
              </button>
              <button id="btnCustomerUserClear" type="button" class="btn toolbar-btn">
                <i class="bi bi-eraser me-1"></i>Limpiar
              </button>
            </div>
          </div>
          <div class="muted small mt-3">El teléfono será el usuario de acceso. En edición puedes actualizar datos, cambiar contraseña y habilitar o deshabilitar el acceso.</div>
        </div>

        <div class="glass panel-pad">
          <div class="section-title mb-3"><i class="bi bi-people-fill text-warning"></i> Usuarios registrados</div>
          <div class="table-wrap">
            <div class="table-responsive">
              <table class="table table-hover align-middle">
                <thead>
                  <tr>
                    <th>Nombre</th>
                    <th>Usuario</th>
                    <th>Rol</th>
                    <th>Correo</th>
                    <th>Estatus</th>
                    <th>Último acceso</th>
                    <th class="text-end">Acciones</th>
                  </tr>
                </thead>
                <tbody id="tbCustomerUsers"></tbody>
              </table>
            </div>
          </div>
          <div class="muted small mt-3" id="customerUsersEmptyHint"></div>
        </div>
      </div>
      <div class="modal-footer">
        <button type="button" class="btn toolbar-btn" data-bs-dismiss="modal">Cerrar</button>
      </div>
    </div>
  </div>
</div>

<!-- Modal productos -->
<div class="modal fade" id="productsModal" tabindex="-1" aria-hidden="true">
  <div class="modal-dialog modal-xl modal-dialog-centered modal-dialog-scrollable">
    <div class="modal-content">
      <div class="modal-header">
        <div>
          <div class="fw-bold fs-5">Artículos comerciales</div>
          <div class="muted small">Activar o desactivar artículos visibles para operación comercial.</div>
        </div>
        <button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
      </div>
      <div class="modal-body">
        <div class="row g-3 mb-3">
          <div class="col-12 col-md-6">
            <label class="form-label">Buscar artículo</label>
            <input id="productSearch" class="form-control" placeholder="SKU o nombre del artículo">
          </div>
        </div>

        <div class="table-wrap">
          <div class="table-responsive">
            <table class="table table-hover align-middle">
              <thead>
                <tr>
                  <th>SKU</th>
                  <th>Artículo</th>
                  <th>UOM</th>
                  <th>Estatus</th>
                  <th class="text-end">Acción</th>
                </tr>
              </thead>
              <tbody id="tbProducts"></tbody>
            </table>
          </div>
        </div>
        <div class="muted small mt-3" id="productsEmptyHint"></div>
      </div>
      <div class="modal-footer">
        <button type="button" class="btn toolbar-btn" data-bs-dismiss="modal">Cerrar</button>
      </div>
    </div>
  </div>
</div>




<!-- Modal nueva orden de salida -->
<div class="modal fade" id="newOrderModal" tabindex="-1" aria-hidden="true">
  <div class="modal-dialog modal-xl modal-dialog-centered modal-dialog-scrollable">
    <div class="modal-content">
      <div class="modal-header">
        <div>
          <div class="fw-bold fs-5">Nueva orden de salida de almacén</div>
          <div class="muted small">Ventas puede solicitar artículos a almacén. La orden quedará autorizada y visible para el módulo de Almacén.</div>
        </div>
        <button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
      </div>

      <div class="modal-body">
        <input type="hidden" id="newOrderId">
        <div class="glass panel-pad mb-4">
          <div class="section-title mb-3">
            <i class="bi bi-clipboard-plus text-primary"></i>
            Datos de la orden
          </div>

          <div class="row g-3">
            <div class="col-12 col-md-4">
              <label class="form-label">Planta / ubicación</label>
              <select id="newOrderLocation" class="form-select"></select>
            </div>
            <div class="col-12 col-md-4">
              <label class="form-label">Cliente / destino</label>
              <input id="newOrderCustomer" class="form-control" placeholder="Ej. Cliente mostrador, donación, ruta, etc.">
            </div>
            <div class="col-12 col-md-4">
              <label class="form-label">Teléfono</label>
              <input id="newOrderPhone" class="form-control" placeholder="Opcional">
            </div>
            <div class="col-12 col-md-3">
              <label class="form-label">Fecha solicitada</label>
              <input id="newOrderDate" type="date" class="form-control">
            </div>
            <div class="col-12 col-md-3">
              <label class="form-label">Hora solicitada</label>
              <input id="newOrderTime" type="time" class="form-control">
            </div>
            <div class="col-12 col-md-3">
              <label class="form-label">Ruta</label>
              <select id="newOrderRoute" class="form-select">
                <option value="">Selecciona ruta</option>
                <?php for ($i=1; $i<=20; $i++): ?>
                  <option value="<?= $i ?>">Ruta <?= $i ?></option>
                <?php endfor; ?>
              </select>
            </div>
            <div class="col-12 col-md-3">
              <label class="form-label">Dirección / referencia</label>
              <input id="newOrderAddress" class="form-control" placeholder="Opcional">
            </div>
            <div class="col-12">
              <label class="form-label">Comentarios</label>
              <input id="newOrderComments" class="form-control" placeholder="Notas para almacén o reparto">
            </div>
          </div>
        </div>

        <div class="glass panel-pad mb-4">
          <div class="d-flex flex-wrap justify-content-between gap-2 align-items-end mb-3">
            <div>
              <div class="section-title">
                <i class="bi bi-box-seam text-primary"></i>
                Artículos
              </div>
              <div class="muted small">Selecciona producto y cantidad. Puedes consultar la existencia disponible por ubicación.</div>
            </div>
            <button id="btnNewOrderRefreshStock" type="button" class="btn toolbar-btn">
              <i class="bi bi-arrow-repeat me-1"></i>Actualizar existencia
            </button>
          </div>

          <div class="row g-3 align-items-end">
            <div class="col-12 col-md-7">
              <label class="form-label">Artículo</label>
              <select id="newOrderProduct" class="form-select"></select>
            </div>
            <div class="col-12 col-md-3">
              <label class="form-label">Cantidad</label>
              <input id="newOrderQty" type="number" min="1" step="1" class="form-control" placeholder="0">
            </div>
            <div class="col-12 col-md-2">
              <button id="btnNewOrderAddItem" type="button" class="btn btn-primary-soft w-100">
                <i class="bi bi-plus-circle me-1"></i>Agregar
              </button>
            </div>
          </div>

          <div id="newOrderStockInfo" class="muted small mt-3"></div>

          <div class="table-wrap mt-3">
            <div class="table-responsive">
              <table class="table table-hover align-middle">
                <thead>
                  <tr>
                    <th>SKU</th>
                    <th>Producto</th>
                    <th class="text-end">Cantidad</th>
                    <th>UOM</th>
                    <th class="text-end">Disponible</th>
                    <th class="text-end">Acción</th>
                  </tr>
                </thead>
                <tbody id="newOrderItemsBody"></tbody>
              </table>
            </div>
          </div>
          <div class="muted small mt-3" id="newOrderItemsHint">Agrega al menos un artículo para guardar la orden.</div>
        </div>

        <div id="newOrderResult"></div>
      </div>

      <div class="modal-footer">
        <button type="button" class="btn toolbar-btn" data-bs-dismiss="modal">Cerrar</button>
        <button type="button" id="btnNewOrderSave" class="btn btn-green-soft">
          <i class="bi bi-check-circle me-1"></i>Crear orden
        </button>
      </div>
    </div>
  </div>
</div>

<!-- Modal confirmar creación / edición de orden -->
<div class="modal fade" id="newOrderConfirmModal" tabindex="-1" aria-hidden="true">
  <div class="modal-dialog modal-lg modal-dialog-centered">
    <div class="modal-content">
      <div class="modal-header">
        <div>
          <div class="fw-bold fs-5" id="newOrderConfirmTitle">Confirmar orden</div>
          <div class="muted small">Valida la información antes de guardar la orden para Almacén.</div>
        </div>
        <button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
      </div>
      <div class="modal-body">
        <div id="newOrderConfirmBody" class="alert-soft"></div>
      </div>
      <div class="modal-footer">
        <button type="button" class="btn toolbar-btn" data-bs-dismiss="modal">No, revisar</button>
        <button type="button" id="btnNewOrderConfirmYes" class="btn btn-green-soft">
          <i class="bi bi-check-circle me-1"></i>Sí, procesar
        </button>
      </div>
    </div>
  </div>
</div>

<!-- Modal resumen orden creada / actualizada -->
<div class="modal fade" id="newOrderSummaryModal" tabindex="-1" aria-hidden="true">
  <div class="modal-dialog modal-lg modal-dialog-centered modal-dialog-scrollable">
    <div class="modal-content">
      <div class="modal-header">
        <div>
          <div class="fw-bold fs-5">Resumen de orden de salida</div>
          <div class="muted small">La orden quedó registrada y visible para el área de Almacén.</div>
        </div>
        <button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
      </div>
      <div class="modal-body">
        <div id="newOrderSummaryBody" class="alert-soft"></div>
      </div>
      <div class="modal-footer">
        <button type="button" id="btnNewOrderSummaryFinish" class="btn btn-primary-soft">
          <i class="bi bi-check2-circle me-1"></i>Finalizar
        </button>
      </div>
    </div>
  </div>
</div>

<!-- Modal confirmar / resultado salida -->
<div class="modal fade" id="dispatchModal" tabindex="-1" aria-hidden="true">
  <div class="modal-dialog modal-lg modal-dialog-centered">
    <div class="modal-content">
      <div class="modal-header">
        <div>
          <div class="fw-bold fs-5" id="dispatchTitle">Confirmar salida</div>
          <div class="muted small" id="dispatchSubtitle">Ventas generará la salida de almacén para el pedido seleccionado.</div>
        </div>
        <button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
      </div>
      <div class="modal-body">
        <input type="hidden" id="dispatchOrderId">
        <div id="dispatchBody" class="alert-soft"></div>
        <div class="mt-3">
          <label class="form-label">Notas de ventas para la salida</label>
          <input id="dispatchNotes" class="form-control" placeholder="Opcional">
        </div>
        <div id="dispatchWarnings" class="mt-3"></div>
      </div>
      <div class="modal-footer">
        <button type="button" class="btn toolbar-btn" data-bs-dismiss="modal">Cerrar</button>
        <button type="button" id="btnConfirmDispatch" class="btn btn-green-soft">
          <i class="bi bi-check-circle me-1"></i>Confirmar y generar salida
        </button>
        <a id="btnDispatchPdf" class="btn btn-primary-soft d-none" href="#" target="_blank" rel="noopener">
          <i class="bi bi-file-earmark-pdf me-1"></i>Descargar PDF
        </a>
      </div>
    </div>
  </div>
</div>

<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
<script>
const CSRF = <?= json_encode($csrf) ?>;
const CAN_MANAGE = <?= json_encode($canManage) ?>;
const CURRENT_LOCATION_ID = <?= json_encode($currentLocationId) ?>;
const IS_HIELO_BARRA_SCOPE = <?= json_encode($isHieloBarraScope) ?>;

const $ = id => document.getElementById(id);

function h(s){
  return String(s ?? '').replace(/[&<>"]/g, m => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'}[m]));
}
function num(v){
  const n = parseFloat(v);
  return isNaN(n) ? 0 : n;
}
function money(v){ return num(v).toFixed(2); }
function qty(v){ return String(Math.round(num(v))); }
function qtyBar(v){ return String(Math.round(num(v))); }
function dt(v){
  if(!v) return '';
  const s = String(v).replace('T', ' ');
  return s.length >= 16 ? s.slice(0,16) : s;
}
function isBarSku(sku){ return String(sku || '').trim().toUpperCase() === 'BARRA 150 KILOS'; }
function statusClass(st){
  st = String(st || '').toUpperCase();
  if (st === 'PENDIENTE') return 'st-pendiente';
  if (st === 'EN_REVISION') return 'st-revision';
  if (st === 'AUTORIZADA') return 'st-autorizada';
  if (st === 'RECHAZADA') return 'st-rechazada';
  if (st === 'CANCELADA') return 'st-cancelada';
  if (st === 'SURTIENDO') return 'st-surtiendo';
  if (st === 'EN_RUTA') return 'st-en_ruta';
  if (st === 'ENTREGADA') return 'st-entregada';
  return 'st-pendiente';
}
function statusLabel(st){
  st = String(st || '').toUpperCase();
  const map = {
    'PENDIENTE': 'Pendiente',
    'EN_REVISION': 'En revisión',
    'AUTORIZADA': 'Autorizada',
    'RECHAZADA': 'Rechazada',
    'CANCELADA': 'Cancelada',
    'SURTIENDO': 'Surtiendo',
    'EN_RUTA': 'En ruta',
    'ENTREGADA': 'Entregada'
  };
  return map[st] || st;
}
function customerRoleLabel(v){
  return String(v || '').toLowerCase() === 'tienda' ? 'Tienda' : 'Cliente';
}

async function apiGet(action, params = {}){
  const u = new URL(location.href);
  u.search = '';
  u.searchParams.set('action', action);
  Object.entries(params).forEach(([k,v]) => {
    if (v !== '' && v !== null && v !== undefined) u.searchParams.set(k, v);
  });

  const r = await fetch(u.toString(), {
    credentials: 'same-origin',
    headers: {'X-CSRF': CSRF}
  });

  const txt = await r.text();
  try { return JSON.parse(txt); }
  catch(e){
    console.error(txt);
    return {ok:false,error:'NO_JSON'};
  }
}

async function apiPost(action, payload = {}){
  const u = new URL(location.href);
  u.search = '';
  u.searchParams.set('action', action);

  const r = await fetch(u.toString(), {
    method: 'POST',
    credentials: 'same-origin',
    headers: {
      'Content-Type': 'application/json',
      'X-CSRF': CSRF
    },
    body: JSON.stringify(payload)
  });

  const txt = await r.text();
  try { return JSON.parse(txt); }
  catch(e){
    console.error(txt);
    return {ok:false,error:'NO_JSON'};
  }
}

let detailModal;
let inventoryModal;
let customersModal;
let customerUsersModal;
let productsModal;
let customerEditModal;
let customerAddressesModal;
let dispatchModal;
let newOrderModal;
let newOrderConfirmModal;
let newOrderSummaryModal;
let pendingNewOrderPayload = null;
let newOrderStockRows = [];
let newOrderItems = [];
let currentRows = [];
let currentView = 'active';
let mailboxRows = [];
let mailboxFolder = 'all';
let mailboxSelectedIndex = -1;
let inventoryLocations = [];
let currentCustomerUsers = [];
let currentCustomerAccess = { customerId: 0, customerName: '' };
let customerTimer = null;
let productTimer = null;

async function loadLocationFilters(){
  const j = await apiGet('location_options');
  if(!j.ok){
    alert(j.error || 'No se pudieron cargar las ubicaciones');
    return;
  }

  inventoryLocations = j.rows || [];

  $('fLocation').innerHTML = '<option value="">Todas</option>' +
    inventoryLocations.map(r => `<option value="${r.id}">${h(r.name)}${r.code ? ' (' + h(r.code) + ')' : ''}</option>`).join('');

  $('invLocationId').innerHTML = inventoryLocations.length
    ? inventoryLocations.map(r => `<option value="${r.id}">${h(r.name)}${r.code ? ' (' + h(r.code) + ')' : ''}</option>`).join('')
    : `<option value="">Sin ubicaciones</option>`;

  if ($('fLocation') && CURRENT_LOCATION_ID > 0) $('fLocation').value = String(CURRENT_LOCATION_ID);
  if ($('invLocationId') && CURRENT_LOCATION_ID > 0) $('invLocationId').value = String(CURRENT_LOCATION_ID);
}

async function loadOrders(){
  const j = await apiGet('orders_mailbox', {
    date_from: $('fDateFrom').value,
    date_to: $('fDateTo').value,
    customer: $('fCustomer').value.trim(),
    location_id: $('fLocation').value
  });

  if(!j.ok){
    alert(j.error || 'Error al cargar pedidos');
    return;
  }

  mailboxRows = j.rows || [];
  currentRows = mailboxRows;

  const statuses = row => String(row.status || '').toUpperCase();
  $('mailCountAll').textContent = String(mailboxRows.length);
  $('mailCountReview').textContent = String(mailboxRows.filter(x => ['PENDIENTE','EN_REVISION'].includes(statuses(x))).length);
  $('mailCountAuthorized').textContent = String(mailboxRows.filter(x => statuses(x) === 'AUTORIZADA' && !Number(x.outbound_id || 0)).length);
  $('mailCountShipping').textContent = String(mailboxRows.filter(x => ['SURTIENDO','EN_RUTA'].includes(statuses(x)) || (Number(x.outbound_id || 0) > 0 && statuses(x) !== 'ENTREGADA')).length);
  $('mailCountDone').textContent = String(mailboxRows.filter(x => statuses(x) === 'ENTREGADA').length);
  $('mailCountCancelled').textContent = String(mailboxRows.filter(x => ['CANCELADA','RECHAZADA'].includes(statuses(x))).length);

  renderOrders();
  await loadSalesInsights();
}

function orderPdfUrl(row){
  const folio = row.sales_order_ref ? row.sales_order_ref : ('PED-' + row.id);
  let url = `orden_entrega_pdf.php?order_id=${encodeURIComponent(row.id)}&folio=${encodeURIComponent(folio)}`;
  if(Number(row.outbound_id || 0) > 0){
    url += `&outbound_id=${encodeURIComponent(row.outbound_id)}`;
  }
  return url;
}

function mailboxStatusInfo(status){
  const s = String(status || '').toUpperCase();
  if (['PENDIENTE','EN_REVISION'].includes(s)) return {cls:'review', label:statusLabel(s)};
  if (s === 'AUTORIZADA') return {cls:'authorized', label:'Autorizada'};
  if (['SURTIENDO','EN_RUTA'].includes(s)) return {cls:'shipping', label:statusLabel(s)};
  if (s === 'ENTREGADA') return {cls:'done', label:'Entregada'};
  if (['CANCELADA','RECHAZADA'].includes(s)) return {cls:'cancelled', label:statusLabel(s)};
  return {cls:'pending', label:statusLabel(s)};
}

function mailboxFilteredRows(){
  return mailboxRows.filter(r => {
    const s = String(r.status || '').toUpperCase();
    if (mailboxFolder === 'review') return ['PENDIENTE','EN_REVISION'].includes(s);
    if (mailboxFolder === 'authorized') return s === 'AUTORIZADA' && !Number(r.outbound_id || 0);
    if (mailboxFolder === 'shipping') return ['SURTIENDO','EN_RUTA'].includes(s) || (Number(r.outbound_id || 0) > 0 && s !== 'ENTREGADA');
    if (mailboxFolder === 'done') return s === 'ENTREGADA';
    if (mailboxFolder === 'cancelled') return ['CANCELADA','RECHAZADA'].includes(s);
    return true;
  });
}

function renderOrders(){
  const rows = mailboxFilteredRows();
  const names = {
    all:'Todos los pedidos',
    review:'Pedidos por revisar',
    authorized:'Pedidos autorizados por surtir',
    shipping:'Pedidos en proceso',
    done:'Pedidos entregados',
    cancelled:'Pedidos cancelados / rechazados'
  };
  $('mailFolderTitle').textContent = names[mailboxFolder] || names.all;
  $('mailFolderSub').textContent = `${rows.length} registro(s) visibles`;

  if(!rows.length){
    $('mailItems').innerHTML = `<div class="mail-empty"><i class="bi bi-inbox fs-2 mb-2"></i><div>Sin pedidos en esta carpeta.</div></div>`;
    $('mailDetail').innerHTML = `<div class="mail-empty"><i class="bi bi-envelope-open fs-1 mb-2"></i><div class="fw-bold">Sin registros para mostrar</div></div>`;
    return;
  }

  $('mailItems').innerHTML = rows.map((r, idx) => {
    const folio = r.sales_order_ref || ('PED-' + r.id);
    const state = mailboxStatusInfo(r.status);
    const client = r.business_name || r.contact_name || 'Cliente';
    const route = r.route_no ? `Ruta ${r.route_no}` : 'Sin ruta';
    return `
      <button type="button" class="mail-item ${idx === 0 ? 'active' : ''}" data-mail-index="${idx}">
        <div class="d-flex justify-content-between gap-2 align-items-start">
          <div class="mail-folio mono">${h(folio)}</div>
          <span class="mail-status ${state.cls}">${h(state.label)}</span>
        </div>
        <div class="mail-sub">${h(client)}</div>
        <div class="mail-bottom">
          <span>${h(r.requested_delivery_date || '')} · ${h(route)}</span>
          <span class="mono fw-bold">${h(qty(r.total_qty || 0))} pz</span>
        </div>
      </button>
    `;
  }).join('');

  selectMailboxOrder(0);
}

async function selectMailboxOrder(index){
  const rows = mailboxFilteredRows();
  const r = rows[index];
  if(!r) return;
  mailboxSelectedIndex = index;
  document.querySelectorAll('#mailItems .mail-item').forEach((el,i) => el.classList.toggle('active', i === index));

  const detail = await apiGet('order_detail', {order_id:r.id});
  if(!detail.ok){
    $('mailDetail').innerHTML = `<div class="mail-empty">No fue posible consultar el detalle del pedido.</div>`;
    return;
  }
  const o = detail.order || r;
  const items = detail.items || [];
  const folio = o.sales_order_ref || ('PED-' + o.id);
  const state = mailboxStatusInfo(o.status);
  const locText = `${o.location_name || ''}${o.location_code ? ' (' + o.location_code + ')' : ''}`;
  const canDispatch = CAN_MANAGE && String(o.status || '').toUpperCase() === 'AUTORIZADA' && !Number(o.outbound_id || 0);
  const canEdit = CAN_MANAGE && String(o.source || '').toUpperCase() === 'VENTAS' && !Number(o.outbound_id || 0) && ['AUTORIZADA','PENDIENTE','EN_REVISION'].includes(String(o.status || '').toUpperCase());
  const hasPdf = Number(o.outbound_id || 0) > 0;

  const itemsHtml = items.length ? items.map(it => `
    <tr>
      <td><div class="mono fw-bold">${h(it.sku || '')}</div><div class="small muted">${h(it.product_name || '')}</div></td>
      <td class="text-end mono fw-bold">${h(qty(it.qty || 0))}</td>
      <td>${h(it.uom || '')}</td>
    </tr>
  `).join('') : '<tr><td colspan="3" class="small muted">Sin artículos.</td></tr>';

  $('mailDetail').innerHTML = `
    <div class="d-flex justify-content-between gap-2 align-items-start flex-wrap mb-3">
      <div>
        <div class="small muted">Folio</div>
        <div class="fw-bold fs-5 mono">${h(folio)}</div>
        <div class="small muted">${h(o.business_name || o.contact_name || '')}</div>
      </div>
      <span class="mail-status ${state.cls}">${h(state.label)}</span>
    </div>
    <div class="row g-2 mb-3">
      <div class="col-6"><div class="mail-detail-card h-100"><div class="mail-detail-label">Entrega</div><div class="mail-detail-value mono">${h(o.requested_delivery_date || '—')} ${h(o.requested_delivery_time || '')}</div></div></div>
      <div class="col-6"><div class="mail-detail-card h-100"><div class="mail-detail-label">Planta</div><div class="mail-detail-value">${h(locText || '—')}</div></div></div>
      <div class="col-12"><div class="mail-detail-card"><div class="mail-detail-label">Dirección</div><div class="mail-detail-value">${h(o.delivery_address || '—')}</div></div></div>
    </div>
    <div class="table-wrap mb-3">
      <div class="table-responsive">
        <table class="table table-sm align-middle mb-0">
          <thead><tr><th>Artículo</th><th class="text-end">Cantidad</th><th>UOM</th></tr></thead>
          <tbody>${itemsHtml}</tbody>
        </table>
      </div>
    </div>
    <div class="d-flex gap-2 flex-wrap">
      <button class="btn toolbar-btn btn-sm" type="button" onclick="openDetail(${Number(o.id)})"><i class="bi bi-eye me-1"></i>Detalle completo</button>
      ${canEdit ? `<button class="btn toolbar-btn btn-sm" type="button" onclick="openEditWarehouseOrder(${Number(o.id)})"><i class="bi bi-pencil-square me-1"></i>Editar</button>` : ''}
      ${canDispatch ? `<button class="btn btn-green-soft btn-sm" type="button" onclick="openDispatchConfirm(${Number(o.id)})"><i class="bi bi-truck me-1"></i>Generar salida</button>` : ''}
      ${hasPdf ? `<a class="btn btn-primary-soft btn-sm" href="${h(orderPdfUrl(o))}" target="_blank" rel="noopener"><i class="bi bi-file-earmark-pdf me-1"></i>PDF</a>` : ''}
    </div>
  `;
}

async function loadSalesInsights(){
  const locationId = $('fLocation').value || String(CURRENT_LOCATION_ID);
  const j = await apiGet('sales_insights', {
    location_id: locationId,
    date_from: $('fDateFrom').value,
    date_to: $('fDateTo').value
  });
  if(!j.ok){
    $('aiSuggestions').innerHTML = `<div class="ai-alert critical"><div class="ai-alert-title">No se pudo generar el análisis</div>${h(j.error || '')}</div>`;
    return;
  }

  const s = j.summary || {};
  $('aiRange').textContent = `· ${j.filters.date_from} a ${j.filters.date_to}`;
  $('aiOrders').textContent = String(s.orders_total || 0);
  $('aiDispatch').textContent = String(s.pending_dispatch || 0);
  $('aiReview').textContent = String(s.pending_review || 0);
  $('aiRisks').textContent = String(s.risks || 0);
  $('mailInventoryNote').textContent = j.inventory_note || 'Inventario surtible por almacenes de la planta activa.';

  $('aiSuggestions').innerHTML = (j.recommendations || []).map(x => `
    <div class="ai-alert ${h(x.level || 'info')}">
      <div class="ai-alert-title">${h(x.title || '')}</div>
      <div>${h(x.text || '')}</div>
    </div>
  `).join('');

  const risks = j.risks || [];
  $('aiRiskRows').innerHTML = risks.length ? risks.map(x => `
    <div class="ai-row">
      <div>
        <div class="ai-row-title">${h(x.sku || '')}</div>
        <div class="ai-row-sub">Solicitado ${h(qty(x.requested_qty || 0))} · disponible ${h(qty(x.available || 0))}</div>
      </div>
      <span class="ai-tag ${h(x.risk_level || 'warning')}">Faltan ${h(qty(x.shortage || 0))}</span>
    </div>
  `).join('') : '<div class="small muted">No hay faltantes en pedidos autorizados.</div>';

  const customers = j.top_customers || [];
  $('aiCustomers').innerHTML = customers.length ? customers.map(x => `
    <div class="ai-row">
      <div>
        <div class="ai-row-title">${h(x.business_name || x.contact_name || '')}</div>
        <div class="ai-row-sub">${Number(x.order_count || 0)} pedido(s)</div>
      </div>
      <span class="ai-tag">${h(qty(x.qty_requested || 0))} pz</span>
    </div>
  `).join('') : '<div class="small muted">Sin actividad en el periodo.</div>';
}

async function openDetail(orderId){
  const j = await apiGet('order_detail', {order_id: orderId});
  if(!j.ok){
    alert(j.error || 'No se pudo cargar el detalle');
    return;
  }

  const o = j.order || {};
  const items = j.items || [];
  const logs = j.logs || [];
  const folio = o.sales_order_ref ? o.sales_order_ref : ('PED-' + o.id);
  const locText = `${o.location_name || ''}${o.location_code ? ' (' + o.location_code + ')' : ''}`;

  $('mOrderId').value = String(o.id || '');
  $('mNewStatus').value = '';
  $('mComment').value = '';

  $('detailHeader').innerHTML = `
    <div class="col-12 col-md-4">
      <div class="log-item">
        <div class="small">Folio</div>
        <div class="fw-bold mono">${h(folio)}</div>
      </div>
    </div>
    <div class="col-12 col-md-4">
      <div class="log-item">
        <div class="small">Cliente</div>
        <div class="fw-bold">${h(o.business_name || '')}</div>
        <div class="small">${h(o.contact_name || '')}</div>
      </div>
    </div>
    <div class="col-12 col-md-4">
      <div class="log-item">
        <div class="small">Estatus</div>
        <div><span class="badge-status ${statusClass(o.status)}">${h(statusLabel(o.status))}</span></div>
      </div>
    </div>

    <div class="col-12 col-md-4">
      <div class="log-item">
        <div class="small">Fecha solicitada</div>
        <div class="fw-semibold">${h(o.requested_delivery_date || '')} ${h(o.requested_delivery_time || '')}</div>
      </div>
    </div>
    <div class="col-12 col-md-4">
      <div class="log-item">
        <div class="small">Ruta</div>
        <div class="fw-semibold">${o.route_no ? 'Ruta ' + h(o.route_no) : '—'}</div>
      </div>
    </div>
    <div class="col-12 col-md-4">
      <div class="log-item">
        <div class="small">Dirección entrega</div>
        <div class="fw-semibold">${h(o.delivery_address || '')}</div>
      </div>
    </div>
    <div class="col-12 col-md-4">
      <div class="log-item">
        <div class="small">Total</div>
        <div class="fw-bold mono">${money(o.total)}</div>
      </div>
    </div>

    <div class="col-12 col-md-6">
      <div class="log-item">
        <div class="small">Planta / ubicación</div>
        <div class="fw-semibold">${h(locText || '—')}</div>
      </div>
    </div>
    <div class="col-12 col-md-6">
      <div class="log-item">
        <div class="small">Autorizado / rechazado</div>
        <div class="fw-semibold">
          Aprobó: ${h(o.approved_by_name || '—')} ${o.approved_at ? '(' + h(dt(o.approved_at)) + ')' : ''}
          <br>
          Rechazó: ${h(o.rejected_by_name || '—')} ${o.rejected_at ? '(' + h(dt(o.rejected_at)) + ')' : ''}
        </div>
      </div>
    </div>

    <div class="col-12">
      <div class="log-item">
        <div class="small">Comentarios del pedido</div>
        <div class="fw-semibold">${h(o.comments || '—')}</div>
      </div>
    </div>
  `;

  $('detailItems').innerHTML = items.map(it => `
    <tr>
      <td class="mono">${h(it.sku || '')}</td>
      <td>
        <div class="fw-semibold">${h(it.product_name || '')}</div>
        <div class="small">${h(it.notes || '')}</div>
      </td>
      <td class="text-end mono">${num(it.qty).toFixed(2)}</td>
      <td class="mono">${h(it.uom || '')}</td>
      <td class="text-end mono">${money(it.unit_price)}</td>
      <td class="text-end mono fw-bold">${money(it.line_total)}</td>
    </tr>
  `).join('');

  $('detailLogs').innerHTML = logs.length
    ? logs.map(l => `
        <div class="log-item">
          <div class="d-flex justify-content-between flex-wrap gap-2">
            <div>
              <div class="fw-semibold">
                ${h(statusLabel(l.old_status || '—'))} → ${h(statusLabel(l.new_status || ''))}
              </div>
              <div class="small">
                ${h(l.changed_by_name || 'Sistema')} · ${h(l.changed_by_role || '')}
              </div>
              <div class="small">${h(l.comment || '')}</div>
            </div>
            <div class="small mono">${h(dt(l.created_at || ''))}</div>
          </div>
        </div>
      `).join('')
    : `<div class="small muted">Sin movimientos de bitácora.</div>`;

  detailModal.show();
}

async function saveStatus(){
  const orderId = parseInt($('mOrderId').value || '0', 10);
  const newStatus = $('mNewStatus').value;
  const comment = $('mComment').value.trim();

  if(orderId <= 0){
    alert('Pedido inválido');
    return;
  }
  if(!newStatus){
    alert('Selecciona un nuevo estatus');
    return;
  }

  const j = await apiPost('status_update', {
    order_id: orderId,
    new_status: newStatus,
    comment: comment
  });

  if(!j.ok){
    alert(j.error || 'No se pudo actualizar el estatus');
    return;
  }

  detailModal.hide();
  await loadOrders();
}


function switchOrderView(view){
  currentView = view;
  $('tabActive').classList.toggle('active', view === 'active');
  $('tabHistory').classList.toggle('active', view === 'history');
  loadOrders();
}


function todayIso(){
  const d = new Date();
  const m = String(d.getMonth() + 1).padStart(2, '0');
  const day = String(d.getDate()).padStart(2, '0');
  return `${d.getFullYear()}-${m}-${day}`;
}

function newOrderSelectedProduct(){
  const pid = parseInt($('newOrderProduct').value || '0', 10);
  return newOrderStockRows.find(r => Number(r.product_id) === pid) || null;
}

function renderNewOrderProducts(){
  const activeRows = newOrderStockRows
    .filter(r => Number(r.active) === 1)
    .sort((a,b) => String(a.sku || '').localeCompare(String(b.sku || '')));

  $('newOrderProduct').innerHTML = activeRows.length
    ? activeRows.map(r => {
        const available = isBarSku(r.sku) ? qtyBar(r.on_hand) : qty(r.on_hand);
        return `<option value="${Number(r.product_id)}">${h(r.sku)} · ${h(r.name)} · Disp. ${available} ${h(r.uom || '')}</option>`;
      }).join('')
    : '<option value="">Sin artículos activos</option>';

  updateNewOrderStockInfo();
}

function updateNewOrderStockInfo(){
  const r = newOrderSelectedProduct();
  if (!r) {
    $('newOrderStockInfo').innerHTML = 'Selecciona una ubicación para cargar artículos disponibles.';
    return;
  }
  const available = isBarSku(r.sku) ? qtyBar(r.on_hand) : qty(r.on_hand);
  $('newOrderStockInfo').innerHTML = `
    Disponible seleccionado: <strong>${h(available)}</strong> ${h(r.uom || '')}
    · ${h(r.sku || '')} ${h(r.name || '')}
  `;
}

async function loadNewOrderStock(){
  const locationId = parseInt($('newOrderLocation').value || '0', 10);
  if (locationId <= 0) {
    newOrderStockRows = [];
    renderNewOrderProducts();
    return;
  }

  $('newOrderStockInfo').innerHTML = 'Consultando existencia...';
  const j = await apiGet('inventory_by_location', {location_id: locationId});
  if (!j.ok) {
    newOrderStockRows = [];
    renderNewOrderProducts();
    $('newOrderStockInfo').innerHTML = `<span class="text-danger">${h(j.error || 'No se pudo consultar inventario')}</span>`;
    return;
  }

  newOrderStockRows = Array.isArray(j.rows) ? j.rows : [];
  renderNewOrderProducts();
}

function renderNewOrderItems(){
  const body = $('newOrderItemsBody');
  if (!newOrderItems.length) {
    body.innerHTML = '';
    $('newOrderItemsHint').textContent = 'Agrega al menos un artículo para guardar la orden.';
    return;
  }

  body.innerHTML = newOrderItems.map((it, idx) => `
    <tr>
      <td class="mono">${h(it.sku)}</td>
      <td>${h(it.name)}</td>
      <td class="text-end mono">${h(isBarSku(it.sku) ? qtyBar(it.qty) : qty(it.qty))}</td>
      <td>${h(it.uom || '')}</td>
      <td class="text-end mono">${h(isBarSku(it.sku) ? qtyBar(it.available) : qty(it.available))}</td>
      <td class="text-end">
        <button type="button" class="btn btn-danger-soft btn-sm" onclick="removeNewOrderItem(${idx})">
          <i class="bi bi-trash"></i>
        </button>
      </td>
    </tr>
  `).join('');

  $('newOrderItemsHint').textContent = `${newOrderItems.length} artículo(s) agregado(s).`;
}

function removeNewOrderItem(idx){
  newOrderItems.splice(idx, 1);
  renderNewOrderItems();
}

function addNewOrderItem(){
  const r = newOrderSelectedProduct();
  const amount = num($('newOrderQty').value);
  if (!r) {
    $('newOrderResult').innerHTML = `<div class="alert alert-danger">Selecciona un artículo.</div>`;
    return;
  }
  if (amount <= 0) {
    $('newOrderResult').innerHTML = `<div class="alert alert-danger">Captura una cantidad mayor a cero.</div>`;
    return;
  }

  const existing = newOrderItems.find(x => Number(x.product_id) === Number(r.product_id));
  if (existing) {
    existing.qty += amount;
  } else {
    newOrderItems.push({
      product_id: Number(r.product_id),
      sku: String(r.sku || ''),
      name: String(r.name || ''),
      uom: String(r.uom || ''),
      qty: amount,
      available: num(r.on_hand)
    });
  }

  $('newOrderQty').value = '';
  $('newOrderResult').innerHTML = '';
  renderNewOrderItems();
}

async function openNewWarehouseOrder(){
  $('newOrderId').value = '';
  $('newOrderCustomer').value = '';
  $('newOrderPhone').value = '';
  $('newOrderDate').value = todayIso();
  $('newOrderTime').value = '';
  $('newOrderAddress').value = '';
  $('newOrderComments').value = '';
  $('newOrderRoute').value = '';
  $('btnNewOrderSave').innerHTML = $('newOrderId').value ? '<i class="bi bi-check-circle me-1"></i>Actualizar orden' : '<i class="bi bi-check-circle me-1"></i>Crear orden';
  $('newOrderResult').innerHTML = '';
  newOrderItems = [];
  renderNewOrderItems();

  if (!inventoryLocations.length) {
    await loadLocations();
  }

  $('newOrderLocation').innerHTML = inventoryLocations.map(l => `
    <option value="${Number(l.id)}">${h(l.name)} ${l.code ? '(' + h(l.code) + ')' : ''}</option>
  `).join('');

  const filterLoc = $('fLocation')?.value || '';
  if (filterLoc) {
    $('newOrderLocation').value = filterLoc;
  }

  await loadNewOrderStock();
  newOrderModal.show();
}


async function openEditWarehouseOrder(orderId){
  const j = await apiGet('order_detail', {order_id: orderId});
  if(!j.ok){ alert(j.error || 'No se pudo cargar el pedido'); return; }
  const o = j.order || {}; const items = j.items || [];
  await openNewWarehouseOrder();
  $('newOrderId').value = String(o.id || '');
  $('newOrderLocation').value = String(o.location_id || '');
  $('newOrderCustomer').value = o.business_name || '';
  $('newOrderPhone').value = o.phone || '';
  $('newOrderDate').value = o.requested_delivery_date || todayIso();
  $('newOrderTime').value = o.requested_delivery_time || '';
  $('newOrderRoute').value = o.route_no ? String(o.route_no) : '';
  $('newOrderAddress').value = o.delivery_address || '';
  $('newOrderComments').value = (o.comments || '').replace('Orden de salida creada desde módulo Ventas.','').replace(/Ruta\s+\d+/,'').trim();
  await loadNewOrderStock();
  newOrderItems = items.map(it => {
    const stock = (newOrderStockRows || []).find(r => Number(r.product_id) === Number(it.product_id));
    return { product_id: Number(it.product_id), sku: String(it.sku || ''), name: String(it.product_name || ''), uom: String(it.uom || ''), qty: num(it.qty), available: stock ? num(stock.on_hand) : 0 };
  });
  renderNewOrderItems();
  $('btnNewOrderSave').innerHTML = '<i class="bi bi-check-circle me-1"></i>Actualizar orden';
  $('newOrderResult').innerHTML = '<div class="alert alert-info mb-0">Editando orden. Al guardar se reemplazan los artículos actuales.</div>';
}

function buildNewOrderPayload(){
  const locationId = parseInt($('newOrderLocation').value || '0', 10);
  return {
    order_id: parseInt($('newOrderId').value || '0', 10),
    location_id: locationId,
    route_no: parseInt($('newOrderRoute').value || '0', 10),
    customer_name: $('newOrderCustomer').value.trim(),
    customer_phone: $('newOrderPhone').value.trim(),
    requested_delivery_date: $('newOrderDate').value,
    requested_delivery_time: $('newOrderTime').value,
    delivery_address: $('newOrderAddress').value.trim(),
    comments: $('newOrderComments').value.trim(),
    items: newOrderItems.map(it => ({product_id: it.product_id, qty: it.qty}))
  };
}

function newOrderLocationLabel(locationId){
  const loc = inventoryLocations.find(l => Number(l.id) === Number(locationId));
  if (!loc) return '—';
  return `${loc.name || ''}${loc.code ? ' (' + loc.code + ')' : ''}`;
}

function renderNewOrderConfirm(payload){
  const isUpdate = Number(payload.order_id) > 0;
  const routeLabel = payload.route_no ? `Ruta ${payload.route_no}` : 'Sin ruta';
  const itemsHtml = newOrderItems.map(it => `
    <tr>
      <td class="mono">${h(it.sku)}</td>
      <td>${h(it.name)}</td>
      <td class="text-end mono">${h(isBarSku(it.sku) ? qtyBar(it.qty) : qty(it.qty))}</td>
      <td>${h(it.uom || '')}</td>
    </tr>
  `).join('');

  $('newOrderConfirmTitle').textContent = isUpdate ? 'Confirmar actualización de orden' : 'Confirmar generación de pedido';
  $('newOrderConfirmBody').innerHTML = `
    <div class="fw-bold mb-2">${isUpdate ? '¿Deseas actualizar esta orden de salida?' : '¿Deseas generar esta orden de salida?'}</div>
    <div class="row g-2 mb-3">
      <div class="col-12 col-md-6"><strong>Planta:</strong> ${h(newOrderLocationLabel(payload.location_id))}</div>
      <div class="col-12 col-md-6"><strong>Ruta:</strong> ${h(routeLabel)}</div>
      <div class="col-12 col-md-6"><strong>Cliente / destino:</strong> ${h(payload.customer_name || 'Orden de salida Ventas')}</div>
      <div class="col-12 col-md-6"><strong>Fecha:</strong> ${h(payload.requested_delivery_date || '—')} ${h(payload.requested_delivery_time || '')}</div>
      <div class="col-12"><strong>Dirección / referencia:</strong> ${h(payload.delivery_address || 'Solicitud generada desde módulo Ventas')}</div>
    </div>
    <div class="table-wrap">
      <div class="table-responsive">
        <table class="table table-hover align-middle mb-0">
          <thead><tr><th>SKU</th><th>Artículo</th><th class="text-end">Cantidad</th><th>UOM</th></tr></thead>
          <tbody>${itemsHtml}</tbody>
        </table>
      </div>
    </div>
    <div class="mt-3 small muted">Al confirmar, la orden quedará guardada y será visible para Almacén. Todavía no se descuenta inventario hasta que se genere la salida.</div>
  `;
}

async function saveNewWarehouseOrder(){
  const payload = buildNewOrderPayload();
  if (payload.location_id <= 0) {
    $('newOrderResult').innerHTML = `<div class="alert alert-danger">Selecciona la planta / ubicación.</div>`;
    return;
  }
  if (!payload.route_no) {
    $('newOrderResult').innerHTML = `<div class="alert alert-danger">Selecciona la ruta.</div>`;
    return;
  }
  if (!newOrderItems.length) {
    $('newOrderResult').innerHTML = `<div class="alert alert-danger">Agrega al menos un artículo.</div>`;
    return;
  }

  pendingNewOrderPayload = payload;
  $('newOrderResult').innerHTML = '';
  renderNewOrderConfirm(payload);
  newOrderConfirmModal.show();
}

async function executeNewWarehouseOrderSave(){
  const payload = pendingNewOrderPayload;
  if (!payload) {
    newOrderConfirmModal.hide();
    return;
  }

  const isUpdate = Number(payload.order_id) > 0;
  $('btnNewOrderConfirmYes').disabled = true;
  $('btnNewOrderConfirmYes').innerHTML = '<span class="spinner-border spinner-border-sm me-1"></span>Procesando...';
  $('btnNewOrderSave').disabled = true;
  $('btnNewOrderSave').innerHTML = '<span class="spinner-border spinner-border-sm me-1"></span>Procesando...';

  const j = await apiPost('manual_order_create', payload);

  $('btnNewOrderConfirmYes').disabled = false;
  $('btnNewOrderConfirmYes').innerHTML = '<i class="bi bi-check-circle me-1"></i>Sí, procesar';
  $('btnNewOrderSave').disabled = false;
  $('btnNewOrderSave').innerHTML = isUpdate ? '<i class="bi bi-check-circle me-1"></i>Actualizar orden' : '<i class="bi bi-check-circle me-1"></i>Crear orden';

  if (!j.ok) {
    newOrderConfirmModal.hide();
    $('newOrderResult').innerHTML = `<div class="alert alert-danger">${h(j.error || 'No se pudo crear la orden')}</div>`;
    return;
  }

  newOrderConfirmModal.hide();
  const routeLabel = j.route_no ? `Ruta ${j.route_no}` : (payload.route_no ? `Ruta ${payload.route_no}` : '—');
  const itemsHtml = newOrderItems.map(it => `
    <tr>
      <td class="mono">${h(it.sku)}</td>
      <td>${h(it.name)}</td>
      <td class="text-end mono">${h(isBarSku(it.sku) ? qtyBar(it.qty) : qty(it.qty))}</td>
      <td>${h(it.uom || '')}</td>
    </tr>
  `).join('');

  $('newOrderSummaryBody').innerHTML = `
    <div class="alert alert-success mb-3">
      <div class="fw-bold">${h(j.message || (isUpdate ? 'Orden actualizada correctamente.' : 'Orden creada correctamente.'))}</div>
      <div>Folio: <span class="mono">${h(j.folio || '')}</span></div>
      <div>Ruta: <strong>${h(routeLabel)}</strong></div>
      <div>Cliente / destino: <strong>${h(payload.customer_name || 'Orden de salida Ventas')}</strong></div>
      <div>Fecha solicitada: <strong>${h(payload.requested_delivery_date || '—')} ${h(payload.requested_delivery_time || '')}</strong></div>
    </div>
    <div class="section-title mb-2"><i class="bi bi-list-check text-primary"></i> Resumen de artículos solicitados</div>
    <div class="table-wrap">
      <div class="table-responsive">
        <table class="table table-hover align-middle mb-0">
          <thead><tr><th>SKU</th><th>Artículo</th><th class="text-end">Cantidad</th><th>UOM</th></tr></thead>
          <tbody>${itemsHtml}</tbody>
        </table>
      </div>
    </div>
    <div class="mt-3 small muted">Almacén podrá ver esta orden en su sección de pedidos autorizados. El descuento de inventario se realizará cuando se genere la salida.</div>
  `;

  await loadOrders();
  newOrderSummaryModal.show();
}

async function finishNewOrderSummary(){
  newOrderSummaryModal.hide();
  newOrderModal.hide();
  pendingNewOrderPayload = null;
  newOrderItems = [];
  renderNewOrderItems();
  await loadOrders();
}

function openDispatchConfirm(orderId){
  const row = currentRows.find(x => Number(x.id) === Number(orderId));
  if(!row){
    alert('No se encontró el pedido en pantalla. Recarga e intenta de nuevo.');
    return;
  }
  const folio = row.sales_order_ref ? row.sales_order_ref : ('PED-' + row.id);
  $('dispatchOrderId').value = String(orderId);
  $('dispatchNotes').value = '';
  $('dispatchWarnings').innerHTML = '';
  $('btnDispatchPdf').classList.add('d-none');
  $('btnDispatchPdf').setAttribute('href', '#');
  $('btnConfirmDispatch').classList.remove('d-none');
  $('btnConfirmDispatch').disabled = false;
  $('dispatchTitle').textContent = 'Confirmar salida de almacén';
  $('dispatchSubtitle').textContent = 'Confirma para descontar inventario y dejar el PDF listo.';
  $('dispatchBody').innerHTML = `
    <div class="fw-bold mb-2">Pedido ${h(folio)}</div>
    <div>Cliente: <strong>${h(row.business_name || '—')}</strong></div>
    <div>Fecha solicitada: <strong>${h(row.requested_delivery_date || '—')} ${h(row.requested_delivery_time || '')}</strong></div>
    <div class="mt-2">Al confirmar, Ventas generará la salida, descontará inventario disponible y mostrará el botón para descargar el PDF.</div>
  `;
  dispatchModal.show();
}

async function confirmSalesDispatch(){
  const orderId = parseInt($('dispatchOrderId').value || '0', 10);
  if(orderId <= 0){
    alert('Pedido inválido');
    return;
  }

  $('btnConfirmDispatch').disabled = true;
  $('btnConfirmDispatch').innerHTML = '<span class="spinner-border spinner-border-sm me-1"></span>Generando...';

  const j = await apiPost('sales_dispatch_from_sales', {
    order_id: orderId,
    notes: $('dispatchNotes').value.trim()
  });

  $('btnConfirmDispatch').innerHTML = '<i class="bi bi-check-circle me-1"></i>Confirmar y generar salida';

  if(!j.ok){
    $('btnConfirmDispatch').disabled = false;
    $('dispatchWarnings').innerHTML = `<div class="alert alert-danger mb-0">${h(j.error || 'No se pudo generar la salida')}</div>`;
    return;
  }

  const warnings = j.warnings || [];
  $('dispatchTitle').textContent = 'Salida generada';
  $('dispatchSubtitle').textContent = 'El pedido quedó procesado y el PDF está disponible.';
  $('dispatchBody').innerHTML = `
    <div class="fw-bold mb-2">${h(j.message || 'Salida generada correctamente.')}</div>
    <div>Salida: <strong>#${h(j.outbound_id || '')}</strong></div>
    <div class="mt-2">Puedes descargar el PDF de orden de entrega con el botón inferior.</div>
  `;
  $('dispatchWarnings').innerHTML = warnings.length
    ? `<div class="alert alert-warning mb-0"><div class="fw-bold mb-1">Observaciones</div><ul class="mb-0">${warnings.map(w => `<li>${h(w)}</li>`).join('')}</ul></div>`
    : '';
  $('btnConfirmDispatch').classList.add('d-none');
  $('btnDispatchPdf').classList.remove('d-none');
  $('btnDispatchPdf').setAttribute('href', j.pdf_url || '#');

  await loadOrders();
}

function renderInventoryCards(rows){
  const wrap = $('invCardsRow');
  if(!wrap) return;

  const visibles = (rows || [])
    .filter(r => Number(r.active) === 1 && num(r.on_hand) > 0)
    .sort((a,b) => String(a.sku || '').localeCompare(String(b.sku || '')));

  if(!visibles.length){
    wrap.innerHTML = '';
    return;
  }

  wrap.innerHTML = visibles.map(r => {
    const available = isBarSku(r.sku) ? qtyBar(r.on_hand) : qty(r.on_hand);
    const wh = Array.isArray(r.warehouse_breakdown) ? r.warehouse_breakdown : [];
    const chips = wh.length
      ? `<div class="mt-2 d-flex gap-1 flex-wrap">${wh.map(w => `
          <span class="badge rounded-pill text-bg-light border">
            ${h(w.warehouse_code || '')}: <span class="mono">${h(qty(w.qty || 0))}</span>
          </span>
        `).join('')}</div>`
      : '';

    return `
      <div class="col-12 col-md-6 col-xl-4">
        <div class="stock-card h-100">
          <div class="stock-icon"><i class="bi bi-box-seam"></i></div>
          <div class="min-w-0 flex-grow-1">
            <div class="stock-title text-truncate" title="${h(r.sku || '')} · ${h(r.name || '')}">
              ${h(r.sku || '')}
            </div>
            <div class="stock-name text-truncate" title="${h(r.name || '')}">${h(r.name || '')}</div>
            <div class="stock-value mono mt-2">${h(available)}</div>
            <div class="stock-sub">Disponible ${h(r.uom || '')}</div>
            ${chips}
          </div>
        </div>
      </div>
    `;
  }).join('');
}

async function loadInventoryByLocation(){
  const locationId = $('invLocationId').value;
  if(!locationId){
    alert('Selecciona una ubicación');
    return;
  }

  const j = await apiGet('inventory_by_location', {location_id: locationId});
  if(!j.ok){
    alert(j.error || 'No se pudieron cargar las existencias');
    return;
  }

  const rows = j.rows || [];
  const loc = j.location || {};

  $('invLocationInfo').textContent =
    `Ubicación: ${loc.name || ''}${loc.code ? ' (' + loc.code + ')' : ''}. ${j.inventory_note || 'Inventario de venta por almacenes activos de la planta.'}`;

  renderInventoryCards(rows);

  const tb = $('invStockBody');
  tb.innerHTML = '';

  const visibles = rows.filter(r => num(r.on_hand) > 0);

  if(!visibles.length){
    $('invEmptyHint').textContent = 'Sin existencias disponibles en la ubicación seleccionada.';
    return;
  }

  $('invEmptyHint').textContent = '';

  visibles.forEach(r => {
    tb.insertAdjacentHTML('beforeend', `
      <tr>
        <td class="mono">${h(r.sku)}</td>
        <td>
          <div class="fw-semibold">${h(r.name)}</div>
          <div class="small muted">${Array.isArray(r.warehouse_breakdown) && r.warehouse_breakdown.length ? r.warehouse_breakdown.map(w => `${h(w.warehouse_code || '')}: ${h(qty(w.qty || 0))}`).join(' · ') : 'Sin almacén disponible'}</div>
        </td>
        <td class="text-end mono fw-bold">${isBarSku(r.sku) ? qtyBar(r.on_hand) : qty(r.on_hand)}</td>
        <td class="mono">${h(r.uom || '')}</td>
        <td>${Number(r.active) === 1 ? '<span class="pill-active">Activo</span>' : '<span class="pill-inactive">Inactivo</span>'}</td>
      </tr>
    `);
  });
}

async function loadCustomers(){
  const j = await apiGet('customers', { q: $('customerSearch').value.trim() });
  if(!j.ok){
    alert(j.error || 'No se pudieron cargar los clientes');
    return;
  }

  const rows = j.rows || [];
  const tb = $('tbCustomers');
  tb.innerHTML = '';

  if(!rows.length){
    $('customersEmptyHint').textContent = 'Sin clientes para el filtro seleccionado.';
    return;
  }

  $('customersEmptyHint').textContent = '';

  rows.forEach(r => {
    const active = Number(r.active) === 1;
    const customerName = JSON.stringify(String(r.business_name || ''));
    tb.insertAdjacentHTML('beforeend', `
      <tr>
        <td><div class="fw-semibold">${h(r.business_name || '')}</div></td>
        <td>${h(r.contact_name || '')}</td>
        <td class="mono">${h(r.phone || '')}</td>
        <td>${active ? '<span class="pill-active">Activo</span>' : '<span class="pill-inactive">Inactivo</span>'}</td>
        <td class="text-end">
          <div class="d-flex gap-2 justify-content-end flex-wrap">
            <button class="btn toolbar-btn btn-sm" type="button" onclick='openCustomerUsers(${Number(r.id)}, ${customerName})'><i class="bi bi-person-lock me-1"></i>Accesos</button>
            <button class="btn toolbar-btn btn-sm" type="button" onclick='openCustomerAddresses(${Number(r.id)}, ${customerName})'><i class="bi bi-geo-alt me-1"></i>Direcciones</button>
          </div>
        </td>
        <td class="text-end">
          <div class="d-flex gap-2 justify-content-end flex-wrap">
            <button class="btn toolbar-btn btn-sm" type="button" onclick='openCustomerEdit(${encodeURIComponent(JSON.stringify(r))})'><i class="bi bi-pencil me-1"></i>Editar</button>
            <button class="btn ${active ? 'btn-danger-soft' : 'btn-green-soft'} btn-sm" type="button" onclick="toggleCustomer(${Number(r.id)}, ${active ? 0 : 1})">
              ${active ? '<i class="bi bi-slash-circle me-1"></i>Desactivar' : '<i class="bi bi-check-circle me-1"></i>Activar'}
            </button>
          </div>
        </td>
      </tr>
    `);
  });
}


function fillLocationSelect(el, selected=''){
  el.innerHTML = inventoryLocations.map(l => `<option value="${Number(l.id)}">${h(l.name)} ${l.code ? '(' + h(l.code) + ')' : ''}</option>`).join('');
  if(selected) el.value = String(selected);
}
async function openCustomerEdit(encodedRow=''){
  if(!inventoryLocations.length) await loadLocations();
  const row = encodedRow ? JSON.parse(decodeURIComponent(encodedRow)) : {};
  $('ceCustomerId').value = row.id || ''; fillLocationSelect($('ceLocationId'), row.location_id || $('fLocation')?.value || '');
  $('ceBusinessName').value = row.business_name || ''; $('ceContactName').value = row.contact_name || ''; $('cePhone').value = row.phone || ''; $('ceEmail').value = row.email || ''; $('cePostalCode').value = row.postal_code || ''; $('ceAddress').value = row.address || ''; $('ceActive').value = Number(row.active) === 0 ? '0' : '1';
  $('customerEditTitle').textContent = row.id ? 'Editar cliente' : 'Nuevo cliente'; $('customerEditResult').innerHTML = ''; customerEditModal.show();
}
async function saveCustomer(){
  const j = await apiPost('customer_save', { customer_id: parseInt($('ceCustomerId').value || '0',10), location_id: parseInt($('ceLocationId').value || '0',10), business_name: $('ceBusinessName').value.trim(), contact_name: $('ceContactName').value.trim(), phone: $('cePhone').value.trim(), email: $('ceEmail').value.trim(), postal_code: $('cePostalCode').value.trim(), address: $('ceAddress').value.trim(), active: parseInt($('ceActive').value || '1',10) });
  if(!j.ok){ $('customerEditResult').innerHTML = `<div class="alert alert-danger">${h(j.error || 'No se pudo guardar')}</div>`; return; }
  $('customerEditResult').innerHTML = '<div class="alert alert-success">Cliente guardado correctamente.</div>'; await loadCustomers();
}
function clearCustomerAddressForm(){ $('caAddressId').value=''; $('caLabel').value=''; $('caAddress').value=''; $('caPostalCode').value=''; $('caDefault').value='0'; $('caActive').value='1'; }
async function openCustomerAddresses(customerId, customerName){ $('caCustomerId').value = String(customerId || ''); $('caTitleSub').textContent = 'Cliente: ' + String(customerName || '') + '. Registra diferentes direcciones de entrega.'; clearCustomerAddressForm(); customerAddressesModal.show(); await loadCustomerAddresses(); }
async function loadCustomerAddresses(){
  const customerId = parseInt($('caCustomerId').value || '0', 10); const j = await apiGet('customer_addresses', {customer_id: customerId});
  if(!j.ok){ alert(j.error || 'No se pudieron cargar direcciones'); return; }
  const rows = j.rows || []; const tb = $('tbCustomerAddresses'); tb.innerHTML = '';
  if(!rows.length){ $('customerAddressesEmptyHint').textContent = 'Sin direcciones registradas.'; return; }
  $('customerAddressesEmptyHint').textContent = '';
  rows.forEach(r => { const active = Number(r.active) === 1; const safeRow = encodeURIComponent(JSON.stringify(r)); tb.insertAdjacentHTML('beforeend', `<tr><td>${h(r.label || '')}</td><td>${h(r.address || '')}</td><td class="mono">${h(r.postal_code || '')}</td><td>${Number(r.is_default) === 1 ? '<span class="pill-active">Sí</span>' : 'No'}</td><td>${active ? '<span class="pill-active">Activo</span>' : '<span class="pill-inactive">Inactivo</span>'}</td><td class="text-end"><div class="d-flex gap-2 justify-content-end flex-wrap"><button class="btn toolbar-btn btn-sm" type="button" onclick="fillCustomerAddressForm('${safeRow}')">Editar</button><button class="btn ${active ? 'btn-danger-soft' : 'btn-green-soft'} btn-sm" type="button" onclick="toggleCustomerAddress(${Number(r.id)}, ${active ? 0 : 1})">${active ? 'Desactivar' : 'Activar'}</button></div></td></tr>`); });
}
function fillCustomerAddressForm(safeRow){ const r = JSON.parse(decodeURIComponent(safeRow)); $('caAddressId').value = r.id || ''; $('caLabel').value = r.label || ''; $('caAddress').value = r.address || ''; $('caPostalCode').value = r.postal_code || ''; $('caDefault').value = Number(r.is_default) === 1 ? '1' : '0'; $('caActive').value = Number(r.active) === 1 ? '1' : '0'; }
async function saveCustomerAddress(){ const j = await apiPost('customer_address_save', {address_id: parseInt($('caAddressId').value || '0',10), customer_id: parseInt($('caCustomerId').value || '0',10), label: $('caLabel').value.trim(), address: $('caAddress').value.trim(), postal_code: $('caPostalCode').value.trim(), is_default: parseInt($('caDefault').value || '0',10), active: parseInt($('caActive').value || '1',10)}); if(!j.ok){ alert(j.error || 'No se pudo guardar dirección'); return; } clearCustomerAddressForm(); await loadCustomerAddresses(); }
async function toggleCustomerAddress(addressId, active){ const j = await apiPost('customer_address_toggle', {address_id: addressId, active: active}); if(!j.ok){ alert(j.error || 'No se pudo actualizar dirección'); return; } await loadCustomerAddresses(); }

async function toggleCustomer(customerId, active){
  const ok = confirm(active === 1 ? '¿Activar este cliente?' : '¿Desactivar este cliente?');
  if(!ok) return;

  const j = await apiPost('customer_toggle', {
    customer_id: customerId,
    active: active
  });

  if(!j.ok){
    alert(j.error || 'No se pudo actualizar el cliente');
    return;
  }

  await loadCustomers();
}

function clearCustomerUserForm(){
  $('cuEditingUserId').value = '';
  $('cuFullName').value = '';
  $('cuPhone').value = '';
  $('cuEmail').value = '';
  $('cuRole').value = 'cliente';
  $('cuPassword').value = '';
  $('cuActive').value = '1';
}

function fillCustomerUserForm(row){
  $('cuEditingUserId').value = String(row.id || '');
  $('cuFullName').value = row.full_name || '';
  $('cuPhone').value = row.phone || '';
  $('cuEmail').value = row.email || '';
  $('cuRole').value = row.role || 'cliente';
  $('cuPassword').value = '';
  $('cuActive').value = String(Number(row.active) === 1 ? 1 : 0);
}

async function openCustomerUsers(customerId, customerName){
  currentCustomerAccess = { customerId: Number(customerId || 0), customerName: String(customerName || '') };
  $('cuCustomerId').value = String(currentCustomerAccess.customerId || '');
  $('customerUsersTitleSub').textContent = 'Cliente: ' + currentCustomerAccess.customerName + '. Alta, edición, contraseña y activación del acceso del cliente.';
  clearCustomerUserForm();
  customerUsersModal.show();
  await loadCustomerUsers();
}

async function loadCustomerUsers(){
  const customerId = parseInt($('cuCustomerId').value || '0', 10);
  if(customerId <= 0){
    $('tbCustomerUsers').innerHTML = '';
    $('customerUsersEmptyHint').textContent = 'Selecciona un cliente válido.';
    return;
  }

  const j = await apiGet('customer_users', { customer_id: customerId });
  if(!j.ok){
    alert(j.error || 'No se pudieron cargar los accesos del cliente');
    return;
  }

  currentCustomerUsers = j.rows || [];
  const tb = $('tbCustomerUsers');
  tb.innerHTML = '';

  if(!currentCustomerUsers.length){
    $('customerUsersEmptyHint').textContent = 'Este cliente aún no tiene usuarios de acceso.';
    return;
  }

  $('customerUsersEmptyHint').textContent = '';

  currentCustomerUsers.forEach(r => {
    const active = Number(r.active) === 1;
    const safeRow = encodeURIComponent(JSON.stringify(r));
    tb.insertAdjacentHTML('beforeend', `
      <tr>
        <td><div class="fw-semibold">${h(r.full_name || '')}</div></td>
        <td>
          <div class="mono">${h(r.phone || '')}</div>
          <div class="small">Usuario: ${h(r.username || r.phone || '')}</div>
        </td>
        <td>${h(customerRoleLabel(r.role))}</td>
        <td>${h(r.email || '')}</td>
        <td>${active ? '<span class="pill-active">Activo</span>' : '<span class="pill-inactive">Inactivo</span>'}</td>
        <td class="mono">${r.last_login_at ? h(dt(r.last_login_at)) : '—'}</td>
        <td class="text-end">
          <div class="d-flex gap-2 justify-content-end flex-wrap">
            <button class="btn toolbar-btn btn-sm" type="button" onclick="editCustomerUser('${safeRow}')">
              <i class="bi bi-pencil-square me-1"></i>Editar
            </button>
            <button class="btn btn-warning-soft btn-sm" type="button" onclick="changeCustomerUserPassword(${Number(r.id)})">
              <i class="bi bi-key me-1"></i>Contraseña
            </button>
            <button class="btn ${active ? 'btn-danger-soft' : 'btn-green-soft'} btn-sm" type="button" onclick="toggleCustomerUser(${Number(r.id)}, ${active ? 0 : 1})">
              ${active ? '<i class="bi bi-slash-circle me-1"></i>Deshabilitar' : '<i class="bi bi-check-circle me-1"></i>Habilitar'}
            </button>
          </div>
        </td>
      </tr>
    `);
  });
}

function editCustomerUser(encoded){
  try{
    const row = JSON.parse(decodeURIComponent(encoded));
    fillCustomerUserForm(row);
  }catch(e){
    console.error(e);
    alert('No se pudo cargar el usuario para edición');
  }
}

async function saveCustomerUser(){
  const customerId = parseInt($('cuCustomerId').value || '0', 10);
  const userId = parseInt($('cuEditingUserId').value || '0', 10);
  const payload = {
    customer_id: customerId,
    full_name: $('cuFullName').value.trim(),
    phone: $('cuPhone').value.trim(),
    email: $('cuEmail').value.trim(),
    role: $('cuRole').value,
    active: parseInt($('cuActive').value || '1', 10)
  };

  if(userId > 0){
    payload.user_id = userId;
    const j = await apiPost('customer_user_update', payload);
    if(!j.ok){
      alert(j.error || 'No se pudo actualizar el usuario');
      return;
    }
    alert('Usuario actualizado correctamente');
  } else {
    payload.password = $('cuPassword').value;
    const j = await apiPost('customer_user_create', payload);
    if(!j.ok){
      alert(j.error || 'No se pudo crear el usuario');
      return;
    }
    alert('Usuario creado correctamente');
  }

  clearCustomerUserForm();
  await loadCustomerUsers();
}

async function changeCustomerUserPassword(userId){
  const pass = prompt('Captura la nueva contraseña para este usuario:');
  if(pass === null) return;

  const j = await apiPost('customer_user_password', {
    user_id: Number(userId || 0),
    password: String(pass || '')
  });

  if(!j.ok){
    alert(j.error || 'No se pudo actualizar la contraseña');
    return;
  }

  alert('Contraseña actualizada correctamente');
  await loadCustomerUsers();
}

async function toggleCustomerUser(userId, active){
  const ok = confirm(active === 1 ? '¿Habilitar este usuario cliente?' : '¿Deshabilitar este usuario cliente?');
  if(!ok) return;

  const j = await apiPost('customer_user_toggle', {
    user_id: Number(userId || 0),
    active: Number(active || 0)
  });

  if(!j.ok){
    alert(j.error || 'No se pudo actualizar el usuario');
    return;
  }

  await loadCustomerUsers();
}

async function loadProducts(){
  const j = await apiGet('products_admin', { q: $('productSearch').value.trim() });
  if(!j.ok){
    alert(j.error || 'No se pudieron cargar los artículos');
    return;
  }

  const rows = j.rows || [];
  const tb = $('tbProducts');
  tb.innerHTML = '';

  if(!rows.length){
    $('productsEmptyHint').textContent = 'Sin artículos para el filtro seleccionado.';
    return;
  }

  $('productsEmptyHint').textContent = '';

  rows.forEach(r => {
    const active = Number(r.active) === 1;
    tb.insertAdjacentHTML('beforeend', `
      <tr>
        <td class="mono">${h(r.sku || '')}</td>
        <td><div class="fw-semibold">${h(r.name || '')}</div></td>
        <td class="mono">${h(r.uom || '')}</td>
        <td>${active ? '<span class="pill-active">Activo</span>' : '<span class="pill-inactive">Inactivo</span>'}</td>
        <td class="text-end">
          <button class="btn ${active ? 'btn-danger-soft' : 'btn-green-soft'} btn-sm"
                  type="button"
                  onclick="toggleProduct(${Number(r.id)}, ${active ? 0 : 1})">
            ${active ? '<i class="bi bi-slash-circle me-1"></i>Desactivar' : '<i class="bi bi-check-circle me-1"></i>Activar'}
          </button>
        </td>
      </tr>
    `);
  });
}

async function toggleProduct(productId, active){
  const ok = confirm(active === 1 ? '¿Activar este artículo?' : '¿Desactivar este artículo?');
  if(!ok) return;

  const j = await apiPost('product_toggle', {
    product_id: productId,
    active: active
  });

  if(!j.ok){
    alert(j.error || 'No se pudo actualizar el artículo');
    return;
  }

  await loadProducts();
}

document.addEventListener('DOMContentLoaded', async () => {
  detailModal = bootstrap.Modal.getOrCreateInstance(document.getElementById('detailModal'));
  inventoryModal = bootstrap.Modal.getOrCreateInstance(document.getElementById('inventoryModal'));
  customersModal = bootstrap.Modal.getOrCreateInstance(document.getElementById('customersModal'));
  customerUsersModal = bootstrap.Modal.getOrCreateInstance(document.getElementById('customerUsersModal'));
  productsModal = bootstrap.Modal.getOrCreateInstance(document.getElementById('productsModal'));
  customerEditModal = bootstrap.Modal.getOrCreateInstance(document.getElementById('customerEditModal'));
  customerAddressesModal = bootstrap.Modal.getOrCreateInstance(document.getElementById('customerAddressesModal'));
  dispatchModal = bootstrap.Modal.getOrCreateInstance(document.getElementById('dispatchModal'));
  newOrderModal = bootstrap.Modal.getOrCreateInstance(document.getElementById('newOrderModal'));
  newOrderConfirmModal = bootstrap.Modal.getOrCreateInstance(document.getElementById('newOrderConfirmModal'));
  newOrderSummaryModal = bootstrap.Modal.getOrCreateInstance(document.getElementById('newOrderSummaryModal'));

  await loadLocationFilters();
  await loadOrders();

  $('btnReload').addEventListener('click', loadOrders);
  $('btnSaveStatus').addEventListener('click', saveStatus);
  $('btnConfirmDispatch').addEventListener('click', confirmSalesDispatch);
  $('btnNewWarehouseOrder').addEventListener('click', openNewWarehouseOrder);
  $('btnNewOrderRefreshStock').addEventListener('click', loadNewOrderStock);
  $('btnNewOrderAddItem').addEventListener('click', addNewOrderItem);
  $('btnNewOrderSave').addEventListener('click', saveNewWarehouseOrder);
  $('btnNewOrderConfirmYes').addEventListener('click', executeNewWarehouseOrderSave);
  $('btnNewOrderSummaryFinish').addEventListener('click', finishNewOrderSummary);
  $('newOrderLocation').addEventListener('change', loadNewOrderStock);
  $('newOrderProduct').addEventListener('change', updateNewOrderStockInfo);
  document.querySelectorAll('[data-mail-folder]').forEach(btn => {
    btn.addEventListener('click', () => {
      mailboxFolder = btn.dataset.mailFolder || 'all';
      document.querySelectorAll('[data-mail-folder]').forEach(x => x.classList.toggle('active', x === btn));
      renderOrders();
    });
  });
  $('mailItems').addEventListener('click', e => {
    const item = e.target.closest('[data-mail-index]');
    if (item) selectMailboxOrder(parseInt(item.dataset.mailIndex || '0', 10));
  });
  $('btnReloadInsights').addEventListener('click', loadSalesInsights);

  $('btnOpenInventory').addEventListener('click', async () => {
    inventoryModal.show();
    if ($('invLocationId').value) {
      await loadInventoryByLocation();
    }
  });

  $('btnOpenCustomers').addEventListener('click', async () => {
    customersModal.show();
    await loadCustomers();
  });

  $('btnOpenProducts').addEventListener('click', async () => {
    productsModal.show();
    await loadProducts();
  });

  $('btnNewCustomer').addEventListener('click', () => openCustomerEdit(''));
  $('btnCustomerSave').addEventListener('click', saveCustomer);
  $('btnCustomerAddressSave').addEventListener('click', saveCustomerAddress);
  $('btnCustomerAddressClear').addEventListener('click', clearCustomerAddressForm);

  $('btnCustomerUserSave').addEventListener('click', saveCustomerUser);
  $('btnCustomerUserClear').addEventListener('click', clearCustomerUserForm);

  $('btnLoadInventory').addEventListener('click', loadInventoryByLocation);
  $('invLocationId').addEventListener('change', loadInventoryByLocation);

  $('fStatus').addEventListener('change', loadOrders);
  $('fDateFrom').addEventListener('change', loadOrders);
  $('fDateTo').addEventListener('change', loadOrders);
  $('fLocation').addEventListener('change', loadOrders);

  let t = null;
  $('fCustomer').addEventListener('input', () => {
    clearTimeout(t);
    t = setTimeout(loadOrders, 350);
  });

  $('customerSearch').addEventListener('input', () => {
    clearTimeout(customerTimer);
    customerTimer = setTimeout(loadCustomers, 300);
  });

  $('productSearch').addEventListener('input', () => {
    clearTimeout(productTimer);
    productTimer = setTimeout(loadProducts, 300);
  });
});
</script>
</body>
</html>
