<?php
/**
 * transferencias.php
 * ============================================================
 * BReport · TRANSFERENCIAS ENTRE PLANTAS / LOCALIDADES
 * ------------------------------------------------------------
 * Reglas operativas ajustadas:
 * - Barra 150 KILOS para transferencia = CF1 + CF2
 * - CF3 no cuenta como barra disponible para transferir
 * - CUB es proceso interno
 * - Producto terminado = cubicado + entradas manuales - merma - salidas
 * - Disponible = total - en tránsito saliente
 * ------------------------------------------------------------
 * Incluye:
 * - Inventario total / tránsito / por recibir / disponible
 * - Crear transferencia
 * - Ver detalle
 * - Cancelar
 * - Recibir
 * - Indicadores de existencia origen / destino en modal
 * ============================================================
 */

declare(strict_types=1);

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'];
$locationId = (int)$sc['location_id'];

$canWrite = has_role('admin') || has_role('warehouse') || has_role('corp') || has_role('plant_admin');
$action   = (string)($_GET['action'] ?? '');

/* ============================================================
   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');
  $d = json_decode($raw ?: '[]', true);
  return is_array($d) ? $d : [];
}

function need_csrf(): void {
  if (($_SERVER['REQUEST_METHOD'] ?? 'GET') === 'GET') return;
  $hdr = (string)($_SERVER['HTTP_X_CSRF'] ?? '');
  if ($hdr === '') jexit(['ok'=>false,'error'=>'Falta X-CSRF'], 419);
  csrf_verify($hdr);
}

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

function ensure_transfer_cols(PDO $pdo): void {
  $alters = [];
  if (!col_exists($pdo,'transfers','driver_name')) $alters[] = "ADD COLUMN driver_name VARCHAR(80) NULL";
  if (!col_exists($pdo,'transfers','truck_no'))    $alters[] = "ADD COLUMN truck_no VARCHAR(40) NULL";
  if (!col_exists($pdo,'transfers','depart_at'))   $alters[] = "ADD COLUMN depart_at DATETIME NULL";
  if (!col_exists($pdo,'transfers','arrive_at'))   $alters[] = "ADD COLUMN arrive_at DATETIME NULL";
  if ($alters) {
    $pdo->exec("ALTER TABLE transfers " . implode(", ", $alters));
  }
}

function fmt_dt_for_db(string $v): ?string {
  $v = trim($v);
  if ($v === '') return null;
  $v = str_replace('T', ' ', $v);
  if (strlen($v) === 16) $v .= ':00';
  return $v;
}

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

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 get_location_warehouse_map(PDO $pdo, int $companyId, int $locationId): array {
  $st = $pdo->prepare("
    SELECT id, code
    FROM warehouses
    WHERE company_id = ?
      AND location_id = ?
      AND active = 1
  ");
  $st->execute([$companyId, $locationId]);

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


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

function get_product_sku(PDO $pdo, int $companyId, int $productId): string {
  $st = $pdo->prepare("\n    SELECT sku\n    FROM products\n    WHERE company_id = ?\n      AND id = ?\n    LIMIT 1\n  ");
  $st->execute([$companyId, $productId]);
  return (string)($st->fetchColumn() ?: '');
}

function get_location_company_id(PDO $pdo, int $locationId): int {
  $st = $pdo->prepare("\n    SELECT company_id\n    FROM locations\n    WHERE id = ?\n      AND active = 1\n    LIMIT 1\n  ");
  $st->execute([$locationId]);
  return (int)($st->fetchColumn() ?: 0);
}

function find_product_id_by_sku(PDO $pdo, int $companyId, string $sku): int {
  $sku = trim($sku);
  if ($sku === '') return 0;
  $st = $pdo->prepare("\n    SELECT id\n    FROM products\n    WHERE company_id = ?\n      AND UPPER(sku) = UPPER(?)\n      AND active = 1\n    ORDER BY id\n    LIMIT 1\n  ");
  $st->execute([$companyId, $sku]);
  return (int)($st->fetchColumn() ?: 0);
}

function get_product_warehouse_balance(PDO $pdo, int $companyId, int $locationId, int $productId, int $warehouseId): float {
  $st = $pdo->prepare("\n    SELECT COALESCE(SUM(\n      CASE\n        WHEN move_type IN ('IN','ADJ') THEN qty\n        WHEN move_type = 'OUT' THEN -qty\n        ELSE 0\n      END\n    ),0) AS qty\n    FROM inventory_moves\n    WHERE company_id = ?\n      AND location_id = ?\n      AND product_id = ?\n      AND warehouse_id = ?\n  ");
  $st->execute([$companyId, $locationId, $productId, $warehouseId]);
  return (float)$st->fetchColumn();
}

function default_transfer_warehouse_id(PDO $pdo, int $companyId, int $locationId, int $productId, string $sku = ''): ?int {
  if ($sku === '') $sku = get_product_sku($pdo, $companyId, $productId);
  if (is_bar_sku($sku)) {
    return get_warehouse_id_by_code($pdo, $companyId, $locationId, 'CF1')
        ?? get_warehouse_id_by_code($pdo, $companyId, $locationId, 'CF2');
  }
  return get_warehouse_id_by_code($pdo, $companyId, $locationId, 'CF3');
}

function resolve_transfer_out_segments(PDO $pdo, int $companyId, int $locationId, int $productId, float $qty): array {
  /*
    Transferencia real de inventario.
    Regla:
    - BARRA 150 KILOS solo puede salir de CF1/CF2.
    - Producto terminado/bolsas solo puede salir de CF3.
    - No se permite fallback a warehouse NULL ni tomar de CUB, MERMA, APROV-MERMA o SOB-CUB.
    - Si no alcanza, se bloquea la transferencia antes de escribir inventory_moves.
  */
  $sku = get_product_sku($pdo, $companyId, $productId);
  $codes = is_bar_sku($sku) ? ['CF1','CF2'] : ['CF3'];
  $remaining = $qty;
  $segments = [];

  foreach ($codes as $code) {
    $wid = get_warehouse_id_by_code($pdo, $companyId, $locationId, $code);
    if (!$wid) continue;

    $balance = get_product_warehouse_balance($pdo, $companyId, $locationId, $productId, $wid);
    if ($balance <= 0) continue;

    $take = min($remaining, $balance);
    if ($take > 0.000001) {
      $segments[] = ['warehouse_id' => $wid, 'warehouse_code' => $code, 'qty' => $take];
      $remaining -= $take;
    }
    if ($remaining <= 0.000001) break;
  }

  if ($remaining > 0.000001) {
    $available = $qty - $remaining;
    throw new RuntimeException(
      'No hay inventario transferible suficiente para ' . ($sku !== '' ? $sku : ('producto #' . $productId)) .
      '. Solicitado: ' . number_format($qty, 3, '.', '') .
      ', disponible en almacenes permitidos: ' . number_format($available, 3, '.', '') .
      '. Almacenes permitidos: ' . implode('/', $codes) . '.'
    );
  }

  return $segments;
}

function insert_inventory_move(PDO $pdo, int $companyId, int $locationId, ?int $warehouseId, string $workDate, int $productId, float $qty, string $moveType, string $refType, int $refId, string $notes, int $userId): void {
  $st = $pdo->prepare("\n    INSERT INTO inventory_moves (\n      company_id, location_id, warehouse_id, work_date,\n      product_id, qty, move_type, channel_id,\n      ref_type, ref_id, notes, created_by, created_at\n    ) VALUES (\n      ?, ?, ?, ?,\n      ?, ?, ?, NULL,\n      ?, ?, ?, ?, NOW()\n    )\n  ");
  $st->execute([
    $companyId,
    $locationId,
    $warehouseId,
    $workDate,
    $productId,
    $qty,
    $moveType,
    $refType,
    $refId,
    $notes,
    $userId,
  ]);
}

/**
 * Inventario por producto para una ubicación.
 * Regla especial:
 * - Barra 150 KILOS transferible = CF1 + CF2
 * - CF3 NO entra para barra en transferencias
 */
function build_transfer_inventory_snapshot(PDO $pdo, int $companyId, int $locationId): array {
  $barId = get_bar_product_id($pdo, $companyId);
  $whMap = get_location_warehouse_map($pdo, $companyId, $locationId);

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

  $rows = [];
  foreach ($st->fetchAll(PDO::FETCH_ASSOC) as $p) {
    $pid = (int)$p['id'];
    $rows[$pid] = [
      'id'                 => $pid,
      'sku'                => (string)$p['sku'],
      'name'               => (string)$p['name'],
      'uom'                => (string)$p['uom'],
      'qty_bar_cf1'        => 0.0,
      'qty_bar_cf2'        => 0.0,
      'qty_bar_cf3'        => 0.0,
      'qty_production'     => 0.0,
      'qty_cubing_out'     => 0.0,
      'qty_quality_out'    => 0.0,
      'qty_manual_in'      => 0.0,
      'qty_manual_out'     => 0.0,
      'qty_in_transit_out' => 0.0,
      'qty_pending_in'     => 0.0,
      'qty_total'          => 0.0,
      'qty_available'      => 0.0,
    ];
  }

  /*
    FUENTE ÚNICA OFICIAL: inventory_moves.
    Disponible transferible:
    - BARRA 150 KILOS = CF1 + CF2.
    - Bolsas/producto terminado = CF3.
    - CUB, MERMA, APROV-MERMA, SOB-CUB y SIN-WH NO son transferibles por este flujo.
  */
  $st = $pdo->prepare("
    SELECT product_id, warehouse_id, UPPER(move_type) AS move_type, SUM(qty) AS qty
    FROM inventory_moves
    WHERE company_id = ?
      AND location_id = ?
    GROUP BY product_id, warehouse_id, UPPER(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);
    $code = strtoupper(trim((string)($whMap[$wid] ?? '')));
    $typ = strtoupper(trim((string)$r['move_type']));
    $qty = (float)$r['qty'];
    $signed = 0.0;

    if ($typ === 'IN' || $typ === 'ADJ') $signed = $qty;
    elseif ($typ === 'OUT') $signed = -$qty;
    else continue;

    if ($pid === $barId || is_bar_sku((string)$rows[$pid]['sku'])) {
      if ($code === 'CF1') {
        $rows[$pid]['qty_total'] += $signed;
        $rows[$pid]['qty_bar_cf1'] += $signed;
      } elseif ($code === 'CF2') {
        $rows[$pid]['qty_total'] += $signed;
        $rows[$pid]['qty_bar_cf2'] += $signed;
      } elseif ($code === 'CF3') {
        // Solo informativo; CF3 no cuenta para BARRA transferible.
        $rows[$pid]['qty_bar_cf3'] += $signed;
      }
    } else {
      if ($code === 'CF3') {
        $rows[$pid]['qty_total'] += $signed;
      }
    }
  }

  // Informativo: transferencias enviadas y aún no recibidas.
  // No se resta del disponible porque el OUT ya se registró en inventory_moves al enviar.
  $st = $pdo->prepare("
    SELECT ti.product_id, SUM(ti.qty) AS qty
    FROM transfers t
    JOIN transfer_items ti ON ti.transfer_id = t.id
    WHERE t.from_company_id = ?
      AND t.from_location_id = ?
      AND t.status = 'SENT'
    GROUP BY ti.product_id
  ");
  $st->execute([$companyId, $locationId]);

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

  // Informativo: pendientes por recibir; aún no son inventario oficial hasta confirmar recepción.
  $st = $pdo->prepare("
    SELECT ti.product_id, SUM(ti.qty) AS qty
    FROM transfers t
    JOIN transfer_items ti ON ti.transfer_id = t.id
    WHERE t.to_company_id = ?
      AND t.to_location_id = ?
      AND t.status = 'SENT'
    GROUP BY ti.product_id
  ");
  $st->execute([$companyId, $locationId]);

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

  foreach ($rows as &$r) {
    $r['qty_available'] = (float)$r['qty_total'];
    $r['qty_total'] = round((float)$r['qty_total'], 3);
    $r['qty_available'] = round((float)$r['qty_available'], 3);
    $r['qty_bar_cf1'] = round((float)$r['qty_bar_cf1'], 3);
    $r['qty_bar_cf2'] = round((float)$r['qty_bar_cf2'], 3);
    $r['qty_bar_cf3'] = round((float)$r['qty_bar_cf3'], 3);
    $r['qty_in_transit_out'] = round((float)$r['qty_in_transit_out'], 3);
    $r['qty_pending_in'] = round((float)$r['qty_pending_in'], 3);
  }
  unset($r);

  return array_values($rows);
}

function get_product_available_for_transfer(PDO $pdo, int $companyId, int $locationId, int $productId): float {
  $rows = build_transfer_inventory_snapshot($pdo, $companyId, $locationId);
  foreach ($rows as $r) {
    if ((int)$r['id'] === $productId) {
      return (float)$r['qty_available'];
    }
  }
  return 0.0;
}

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

  if ($action === 'destinations') {
    /*
      Transferencias ENTRE EMPRESAS / LOCALIDADES:
      - Se listan todas las localidades activas, incluso de otra empresa.
      - Se excluye únicamente la localidad actual.
      - Si después se requiere limitar por permisos, debe hacerse con una tabla de permisos/rutas.
    */
    $st = $pdo->prepare("
      SELECT l.id, l.name, l.code, l.company_id, c.name AS company_name
      FROM locations l
      JOIN companies c ON c.id = l.company_id
      WHERE l.active = 1
        AND l.id <> ?
      ORDER BY c.name, l.name
    ");
    $st->execute([$locationId]);
    jexit(['ok'=>true, 'rows'=>$st->fetchAll(PDO::FETCH_ASSOC)]);
  }

  if ($action === 'products') {
    $st = $pdo->prepare("
      SELECT id, sku, name, uom
      FROM products
      WHERE company_id = ?
        AND active = 1
      ORDER BY sku
    ");
    $st->execute([$companyId]);
    jexit(['ok'=>true, 'rows'=>$st->fetchAll(PDO::FETCH_ASSOC)]);
  }

  if ($action === 'product_conversions') {
    $st = $pdo->prepare("
      SELECT pc.from_product_id, pc.to_product_id, pc.factor,
             p1.sku AS from_sku, p1.name AS from_name,
             p2.sku AS to_sku,   p2.name AS to_name
      FROM product_conversions pc
      JOIN products p1 ON p1.id = pc.from_product_id
      JOIN products p2 ON p2.id = pc.to_product_id
      WHERE pc.company_id = ?
        AND pc.active = 1
      ORDER BY p1.sku, p2.sku
    ");
    $st->execute([$companyId]);
    jexit(['ok'=>true, 'rows'=>$st->fetchAll(PDO::FETCH_ASSOC)]);
  }

  if ($action === 'inventory_summary') {
    $rows = build_transfer_inventory_snapshot($pdo, $companyId, $locationId);
    jexit(['ok'=>true, 'rows'=>$rows]);
  }

  if ($action === 'product_stock_info') {
    $productId = (int)($_GET['product_id'] ?? 0);
    $toLocationId = (int)($_GET['to_location_id'] ?? 0);

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

    $originAvailable = get_product_available_for_transfer($pdo, $companyId, $locationId, $productId);

    $destinationAvailable = 0.0;
    $destinationProductId = 0;
    $destinationCompanyId = 0;
    $destinationProductMissing = false;

    if ($toLocationId > 0) {
      $destinationCompanyId = get_location_company_id($pdo, $toLocationId);
      $sourceSku = get_product_sku($pdo, $companyId, $productId);
      if ($destinationCompanyId > 0 && $sourceSku !== '') {
        $destinationProductId = find_product_id_by_sku($pdo, $destinationCompanyId, $sourceSku);
        if ($destinationProductId > 0) {
          $destinationAvailable = get_product_available_for_transfer($pdo, $destinationCompanyId, $toLocationId, $destinationProductId);
        } else {
          $destinationProductMissing = true;
        }
      }
    }

    jexit([
      'ok' => true,
      'origin_available' => round($originAvailable, 3),
      'destination_available' => round($destinationAvailable, 3),
      'destination_company_id' => $destinationCompanyId,
      'destination_product_id' => $destinationProductId,
      'destination_product_missing' => $destinationProductMissing,
    ]);
  }

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

    $d = read_json();
    $toLocationId = (int)($d['to_location_id'] ?? 0);
    $notes        = trim((string)($d['notes'] ?? ''));

    if ($toLocationId <= 0) jexit(['ok'=>false,'error'=>'Destino requerido'], 400);
    if ($toLocationId === $locationId) jexit(['ok'=>false,'error'=>'El destino no puede ser el mismo'], 400);

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

    if (!$loc || (int)$loc['active'] !== 1) {
      jexit(['ok'=>false,'error'=>'Destino inválido'], 404);
    }

    $today = date('Y-m-d');
    $toCompanyId = (int)$loc['company_id'];

    $st = $pdo->prepare("
      INSERT INTO transfers(
        company_id, from_company_id, to_company_id,
        from_location_id, to_location_id,
        transfer_date, work_date, status,
        created_by, notes, created_at, transfer_type
      ) VALUES (
        ?, ?, ?, ?, ?, ?, ?, 'DRAFT', ?, ?, NOW(), 'MOVE'
      )
    ");
    $st->execute([
      $companyId,
      $companyId,
      $toCompanyId,
      $locationId,
      $toLocationId,
      $today,
      $today,
      (int)$u['id'],
      ($notes === '' ? null : $notes)
    ]);

    jexit(['ok'=>true, 'id'=>(int)$pdo->lastInsertId()]);
  }

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

    $d = read_json();
    $transferId = (int)($d['transfer_id'] ?? 0);
    $productId  = (int)($d['product_id'] ?? 0);
    $qty        = (float)($d['qty'] ?? 0);
    $convToId   = (int)($d['conversion_to_product_id'] ?? 0);
    $convFactor = ($d['conversion_factor'] ?? null);

    if ($transferId <= 0 || $productId <= 0 || $qty <= 0) {
      jexit(['ok'=>false,'error'=>'Datos inválidos'], 400);
    }
    if ($convToId > 0) {
      jexit(['ok'=>false,'error'=>'Las conversiones están bloqueadas en Transferencias. Usa Producción Cubicado para transformar y después transfiere el producto final.'], 409);
    }

    $st = $pdo->prepare("
      SELECT id, status, from_company_id, from_location_id
      FROM transfers
      WHERE id = ?
      LIMIT 1
    ");
    $st->execute([$transferId]);
    $t = $st->fetch(PDO::FETCH_ASSOC);

    if (!$t) jexit(['ok'=>false,'error'=>'Transferencia no encontrada'], 404);
    if ((string)$t['status'] !== 'DRAFT') jexit(['ok'=>false,'error'=>'Solo se puede editar en DRAFT'], 409);
    if ((int)$t['from_company_id'] !== $companyId || (int)$t['from_location_id'] !== $locationId) {
      jexit(['ok'=>false,'error'=>'Solo el origen puede editar'], 403);
    }

    $available = get_product_available_for_transfer($pdo, $companyId, $locationId, $productId);
    if ($qty > $available) {
      jexit(['ok'=>false,'error'=>'La cantidad excede el disponible del origen'], 409);
    }

    $st = $pdo->prepare("
      INSERT INTO transfer_items(
        transfer_id, product_id, qty, conversion_to_product_id, conversion_factor
      ) VALUES (?, ?, ?, ?, ?)
      ON DUPLICATE KEY UPDATE
        qty = VALUES(qty),
        conversion_to_product_id = VALUES(conversion_to_product_id),
        conversion_factor = VALUES(conversion_factor)
    ");
    $st->execute([
      $transferId,
      $productId,
      $qty,
      $convToId > 0 ? $convToId : null,
      ($convFactor !== null && $convFactor !== '' ? (float)$convFactor : null)
    ]);

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

  if ($action === 'items') {
    $transferId = (int)($_GET['transfer_id'] ?? 0);
    if ($transferId <= 0) jexit(['ok'=>false,'error'=>'transfer_id requerido'], 400);

    $st = $pdo->prepare("
      SELECT
        ti.id,
        ti.transfer_id,
        ti.product_id,
        ti.qty,
        ti.conversion_to_product_id,
        ti.conversion_factor,
        p.sku,
        p.name AS product_name,
        p.uom,
        pc.sku AS conv_sku,
        pc.name AS conv_name
      FROM transfer_items ti
      JOIN products p ON p.id = ti.product_id
      LEFT JOIN products pc ON pc.id = ti.conversion_to_product_id
      WHERE ti.transfer_id = ?
      ORDER BY p.sku
    ");
    $st->execute([$transferId]);

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

  if ($action === 'detail') {
    $transferId = (int)($_GET['transfer_id'] ?? 0);
    if ($transferId <= 0) jexit(['ok'=>false,'error'=>'transfer_id requerido'], 400);

    $st = $pdo->prepare("
      SELECT
        t.*,
        lf.name AS from_location_name,
        lf.code AS from_location_code,
        lt.name AS to_location_name,
        lt.code AS to_location_code
      FROM transfers t
      JOIN locations lf ON lf.id = t.from_location_id
      JOIN locations lt ON lt.id = t.to_location_id
      WHERE t.id = ?
      LIMIT 1
    ");
    $st->execute([$transferId]);
    $transfer = $st->fetch(PDO::FETCH_ASSOC);

    if (!$transfer) jexit(['ok'=>false,'error'=>'Transferencia no encontrada'], 404);

    $st = $pdo->prepare("
      SELECT
        ti.id,
        ti.transfer_id,
        ti.product_id,
        ti.qty,
        ti.conversion_to_product_id,
        ti.conversion_factor,
        p.sku,
        p.name AS product_name,
        p.uom,
        pc.sku AS conv_sku,
        pc.name AS conv_name
      FROM transfer_items ti
      JOIN products p ON p.id = ti.product_id
      LEFT JOIN products pc ON pc.id = ti.conversion_to_product_id
      WHERE ti.transfer_id = ?
      ORDER BY p.sku
    ");
    $st->execute([$transferId]);
    $items = $st->fetchAll(PDO::FETCH_ASSOC);

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

  if ($action === 'list_outbox') {
    $st = $pdo->prepare("
      SELECT
        t.*,
        lf.name AS from_location_name,
        lt.name AS to_location_name
      FROM transfers t
      JOIN locations lf ON lf.id = t.from_location_id
      JOIN locations lt ON lt.id = t.to_location_id
      WHERE t.from_company_id = ?
        AND t.from_location_id = ?
      ORDER BY t.id DESC
      LIMIT 200
    ");
    $st->execute([$companyId, $locationId]);
    jexit(['ok'=>true, 'rows'=>$st->fetchAll(PDO::FETCH_ASSOC)]);
  }

  if ($action === 'list_inbox') {
    $st = $pdo->prepare("
      SELECT
        t.*,
        lf.name AS from_location_name,
        lt.name AS to_location_name
      FROM transfers t
      JOIN locations lf ON lf.id = t.from_location_id
      JOIN locations lt ON lt.id = t.to_location_id
      WHERE t.to_company_id = ?
        AND t.to_location_id = ?
      ORDER BY t.id DESC
      LIMIT 200
    ");
    $st->execute([$companyId, $locationId]);
    jexit(['ok'=>true, 'rows'=>$st->fetchAll(PDO::FETCH_ASSOC)]);
  }

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

    $d = read_json();
    $transferId = (int)($d['transfer_id'] ?? 0);
    $driver     = trim((string)($d['driver_name'] ?? ''));
    $truck      = trim((string)($d['truck_no'] ?? ''));
    $departAt   = fmt_dt_for_db((string)($d['depart_at'] ?? ''));

    if ($transferId <= 0) jexit(['ok'=>false,'error'=>'transfer_id requerido'], 400);
    if ($driver === '')   jexit(['ok'=>false,'error'=>'Chofer requerido'], 400);
    if ($truck === '')    jexit(['ok'=>false,'error'=>'Unidad / transporte requerida'], 400);
    if (!$departAt)       jexit(['ok'=>false,'error'=>'Fecha/hora salida requerida'], 400);

    $pdo->beginTransaction();
    try {
      $st = $pdo->prepare("SELECT * FROM transfers WHERE id = ? FOR UPDATE");
      $st->execute([$transferId]);
      $t = $st->fetch(PDO::FETCH_ASSOC);

      if (!$t) throw new RuntimeException('Transferencia no encontrada');
      if ((string)$t['status'] !== 'DRAFT') throw new RuntimeException('Solo se puede enviar desde DRAFT');
      if ((int)$t['from_company_id'] !== $companyId || (int)$t['from_location_id'] !== $locationId) {
        throw new RuntimeException('Solo el origen puede enviar');
      }

      $st = $pdo->prepare("
        SELECT ti.product_id, ti.qty, ti.conversion_to_product_id, ti.conversion_factor, p.sku, p.name AS product_name
        FROM transfer_items ti
        JOIN products p ON p.id = ti.product_id
        WHERE ti.transfer_id = ?
        ORDER BY p.sku
      ");
      $st->execute([$transferId]);
      $items = $st->fetchAll(PDO::FETCH_ASSOC);

      if (!$items) throw new RuntimeException('Agrega al menos un artículo');

      $workDate = date('Y-m-d', strtotime($departAt));
      if ($workDate === '1970-01-01') $workDate = date('Y-m-d');

      foreach ($items as $it) {
        $productId = (int)$it['product_id'];
        $qty = (float)$it['qty'];
        if ((int)($it['conversion_to_product_id'] ?? 0) > 0) {
          throw new RuntimeException('Las conversiones dentro de transferencias están bloqueadas para no alterar inventario. Usa Cubicado para transformar producto y luego transfiere el producto final.');
        }
        $available = get_product_available_for_transfer($pdo, $companyId, $locationId, $productId);
        if ($qty > $available) {
          throw new RuntimeException('La cantidad excede el disponible del origen para ' . (string)$it['sku']);
        }

        $segments = resolve_transfer_out_segments($pdo, $companyId, $locationId, $productId, $qty);
        foreach ($segments as $seg) {
          insert_inventory_move(
            $pdo,
            (int)$t['from_company_id'],
            (int)$t['from_location_id'],
            $seg['warehouse_id'] !== null ? (int)$seg['warehouse_id'] : null,
            $workDate,
            $productId,
            (float)$seg['qty'],
            'OUT',
            'TRANSFER_OUT',
            $transferId,
            'Transferencia enviada #' . $transferId . ' | ' . (string)$it['sku'],
            (int)$u['id']
          );
        }
      }

      $st = $pdo->prepare("
        UPDATE transfers
        SET status='SENT',
            driver_name=?,
            truck_no=?,
            depart_at=?
        WHERE id=?
          AND status='DRAFT'
      ");
      $st->execute([$driver, $truck, $departAt, $transferId]);

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

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

    $d = read_json();
    $transferId = (int)($d['transfer_id'] ?? 0);

    if ($transferId <= 0) jexit(['ok'=>false,'error'=>'transfer_id requerido'], 400);

    $pdo->beginTransaction();
    try {
      $st = $pdo->prepare("SELECT * FROM transfers WHERE id = ? FOR UPDATE");
      $st->execute([$transferId]);
      $t = $st->fetch(PDO::FETCH_ASSOC);

      if (!$t) throw new RuntimeException('Transferencia no encontrada');
      if ((int)$t['from_company_id'] !== $companyId || (int)$t['from_location_id'] !== $locationId) {
        throw new RuntimeException('Solo el origen puede cancelar');
      }
      if (!in_array((string)$t['status'], ['DRAFT','SENT'], true)) {
        throw new RuntimeException('Solo se puede cancelar si está DRAFT o SENT');
      }

      if ((string)$t['status'] === 'SENT') {
        $st = $pdo->prepare("
          SELECT product_id, warehouse_id, SUM(qty) AS qty
          FROM inventory_moves
          WHERE company_id = ?
            AND location_id = ?
            AND ref_type IN ('TRANSFER','TRANSFER_OUT')
            AND ref_id = ?
            AND move_type = 'OUT'
          GROUP BY product_id, warehouse_id
        ");
        $st->execute([(int)$t['from_company_id'], (int)$t['from_location_id'], $transferId]);
        foreach ($st->fetchAll(PDO::FETCH_ASSOC) as $mv) {
          insert_inventory_move(
            $pdo,
            (int)$t['from_company_id'],
            (int)$t['from_location_id'],
            $mv['warehouse_id'] !== null ? (int)$mv['warehouse_id'] : null,
            date('Y-m-d'),
            (int)$mv['product_id'],
            (float)$mv['qty'],
            'IN',
            'TRANSFER_CANCEL',
            $transferId,
            'Cancelación de transferencia #' . $transferId,
            (int)$u['id']
          );
        }
      }

      $st = $pdo->prepare("
        UPDATE transfers
        SET status='CANCELLED'
        WHERE id=?
          AND status IN ('DRAFT','SENT')
      ");
      $st->execute([$transferId]);

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

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

    $d = read_json();
    $transferId = (int)($d['transfer_id'] ?? 0);
    $arriveAt   = fmt_dt_for_db((string)($d['arrive_at'] ?? ''));

    if ($transferId <= 0) jexit(['ok'=>false,'error'=>'transfer_id requerido'], 400);
    if (!$arriveAt)       jexit(['ok'=>false,'error'=>'Fecha/hora llegada requerida'], 400);

    $pdo->beginTransaction();
    try {
      $st = $pdo->prepare("SELECT * FROM transfers WHERE id = ? FOR UPDATE");
      $st->execute([$transferId]);
      $t = $st->fetch(PDO::FETCH_ASSOC);

      if (!$t) throw new RuntimeException('Transferencia no encontrada');
      if ((string)$t['status'] !== 'SENT') throw new RuntimeException('Solo se puede recibir si está en SENT');
      if ((int)$t['to_company_id'] !== $companyId || (int)$t['to_location_id'] !== $locationId) {
        throw new RuntimeException('Solo el destino puede recibir');
      }

      $st = $pdo->prepare("
        SELECT ti.product_id, ti.qty, p.sku
        FROM transfer_items ti
        JOIN products p ON p.id = ti.product_id
        WHERE ti.transfer_id = ?
      ");
      $st->execute([$transferId]);
      $items = $st->fetchAll(PDO::FETCH_ASSOC);

      if (!$items) throw new RuntimeException('La transferencia no tiene artículos');

      $workDate = date('Y-m-d', strtotime($arriveAt));
      if ($workDate === '1970-01-01') $workDate = date('Y-m-d');

      foreach ($items as $it) {
        $sourceProductId = (int)$it['product_id'];
        $qty       = (float)$it['qty'];
        $sku       = (string)$it['sku'];

        // En transferencia entre empresas, el producto destino se resuelve por SKU.
        // Así evitamos insertar un product_id de la empresa origen dentro del kardex de la empresa destino.
        $destinationProductId = find_product_id_by_sku($pdo, (int)$t['to_company_id'], $sku);
        if ($destinationProductId <= 0) {
          throw new RuntimeException('El producto ' . $sku . ' no existe o está inactivo en la empresa destino. Crea/mapea el producto antes de recibir la transferencia.');
        }

        $warehouseId = default_transfer_warehouse_id($pdo, (int)$t['to_company_id'], (int)$t['to_location_id'], $destinationProductId, $sku);
        if ($warehouseId === null || $warehouseId <= 0) {
          throw new RuntimeException('No se encontró almacén destino permitido para ' . $sku . '. BARRA debe entrar a CF1/CF2 y producto terminado a CF3.');
        }

        insert_inventory_move(
          $pdo,
          (int)$t['to_company_id'],
          (int)$t['to_location_id'],
          $warehouseId,
          $workDate,
          $destinationProductId,
          $qty,
          'IN',
          'TRANSFER_IN',
          $transferId,
          'Transferencia recibida #' . $transferId . ' | ' . $sku . ' | product_id origen ' . $sourceProductId,
          (int)$u['id']
        );
      }

      $st = $pdo->prepare("
        UPDATE transfers
        SET status='RECEIVED',
            received_by=?,
            received_at=NOW(),
            arrive_at=?
        WHERE id=?
      ");
      $st->execute([(int)$u['id'], $arriveAt, $transferId]);

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

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

/* ===================== UI ===================== */
$csrf = csrf_token();

$st = $pdo->prepare("SELECT name FROM companies WHERE id=?");
$st->execute([$companyId]);
$companyName = (string)($st->fetchColumn() ?: '');

$st = $pdo->prepare("SELECT name FROM locations WHERE id=?");
$st->execute([$locationId]);
$locationName = (string)($st->fetchColumn() ?: '');
?>
<!doctype html>
<html lang="es">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>BReport · Transferencias</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;
      --panel:rgba(7,25,57,.80);
      --panel2:rgba(10,31,70,.90);
      --line:rgba(145,188,255,.14);
      --txt:#eef4ff;
      --txt-strong:#ffffff;
      --txt-soft:#c7d8f4;
      --muted:#9fb6d9;
      --muted-2:#86a2cd;
      --blue:#2f7bff;
      --blue2:#57a5ff;
      --gold:#f4be59;
      --gold-soft:#ffe19b;
      --green:#72f0a5;
      --danger:#ff8fa3;
      --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 10% 18%, rgba(28,112,255,.24), transparent 24%),
        radial-gradient(circle at 88% 82%, rgba(0,190,255,.12), transparent 20%),
        linear-gradient(135deg, #021026 0%, #03152e 42%, #071d3b 100%);
      background-attachment:fixed;
      -webkit-font-smoothing:antialiased;
      -moz-osx-font-smoothing:grayscale;
    }

    .topbar{
      background:rgba(3,16,40,.84);
      backdrop-filter:blur(12px);
      border-bottom:1px solid rgba(255,255,255,.07);
      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:1380px;margin:0 auto}
    .panel-pad{padding:22px}

    .brand-title{
      font-weight:800;
      font-size:1.95rem;
      color:var(--txt-strong);
      line-height:1.1;
      margin:0;
      text-shadow:0 1px 0 rgba(0,0,0,.16);
    }

    .brand-sub{
      color:var(--txt-soft);
      font-size:1rem;
      margin-top:4px;
      font-weight:500;
    }

    .glass{
      background:linear-gradient(180deg, rgba(7,27,63,.96), rgba(3,18,46,.94));
      border:1px solid rgba(145,188,255,.12);
      border-radius:var(--radius);
      box-shadow:var(--shadow);
      backdrop-filter:blur(10px);
    }

    .section-title,
    .fw-bold,
    .fw-semibold,
    .kpi-value,
    .table thead th{
      color:var(--txt-strong);
      text-shadow:0 1px 0 rgba(0,0,0,.18);
    }

    .muted,
    .small,
    .small-note,
    .kpi-label,
    .kpi-hint{
      color:var(--txt-soft) !important;
    }

    .kpi-card{
      position:relative;
      overflow:hidden;
      min-height:140px;
      border-radius:24px;
      border:1px solid rgba(160,198,255,.14);
      background:linear-gradient(180deg, rgba(8,25,60,.98), rgba(4,18,42,.95));
      transition:transform .18s ease,border-color .18s ease, box-shadow .18s ease;
      box-shadow:
        inset 0 1px 0 rgba(255,255,255,.04),
        0 18px 40px rgba(0,0,0,.24);
    }

    .kpi-card::after{
      content:"";
      position:absolute;
      right:-40px;
      top:-40px;
      width:120px;
      height:120px;
      border-radius:50%;
      background:radial-gradient(circle, rgba(87,165,255,.18), transparent 70%);
      pointer-events:none;
    }

    .kpi-card:hover{
      transform:translateY(-3px);
      border-color:rgba(123,179,255,.28);
    }

    .kpi-top{
      display:flex;
      align-items:center;
      gap:12px;
      margin-bottom:10px;
    }

    .kpi-icon{
      width:46px;
      height:46px;
      border-radius:16px;
      display:flex;
      align-items:center;
      justify-content:center;
      font-size:1.2rem;
      color:#fff;
      background:linear-gradient(135deg, rgba(47,123,255,.85), rgba(87,165,255,.85));
      box-shadow:0 12px 22px rgba(36,97,210,.22);
      flex:0 0 auto;
    }

    .kpi-label{
      color:#c2d5f8 !important;
      font-size:.95rem;
      font-weight:700;
      margin:0;
    }

    .kpi-value{
      color:#fff !important;
      font-size:2.2rem;
      font-weight:800;
      letter-spacing:.35px;
      margin:4px 0 2px;
    }

    .kpi-hint{
      color:#bfd1f2 !important;
      font-size:.9rem;
      line-height:1.35;
    }

    .badge-soft{
      display:inline-flex;
      align-items:center;
      gap:8px;
      padding:9px 16px;
      border-radius:999px;
      background:rgba(244,190,89,.12);
      color:#ffd978;
      border:1px solid rgba(244,190,89,.28);
      font-weight:800;
      white-space:nowrap;
      box-shadow:inset 0 1px 0 rgba(255,255,255,.04);
    }

    .small-note{
      border-radius:16px;
      border:1px solid rgba(244,190,89,.22);
      background:rgba(244,190,89,.08);
      color:var(--gold-soft) !important;
      padding:12px 14px;
      font-size:.94rem;
      font-weight:600;
      line-height:1.45;
    }

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

    .table{
      margin:0;
      color:var(--txt);
      --bs-table-bg:transparent;
      --bs-table-striped-bg:rgba(255,255,255,.018);
      --bs-table-hover-bg:rgba(68,124,220,.08);
      --bs-table-border-color:rgba(169,203,255,.06);
    }

    .table thead th{
      color:#f8fbff !important;
      font-weight:800;
      font-size:1rem;
      background:rgba(11,30,70,.88);
      white-space:nowrap;
      border-bottom:1px solid rgba(169,203,255,.10);
      padding-top:1rem;
      padding-bottom:1rem;
    }

    .table tbody td{
      color:#edf4ff !important;
      vertical-align:middle;
      border-color:rgba(169,203,255,.06);
      font-weight:500;
    }

    .table tbody tr:hover td{
      color:#ffffff !important;
    }

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

    .strong-num{
      font-weight:800;
      color:#fff !important;
    }

    .form-label{
      color:#d2e0f8;
      font-weight:700;
      font-size:.92rem;
    }

    .form-control,
    .form-select{
      min-height:50px;
      border-radius:16px;
      color:#fff !important;
      background:rgba(8,24,58,.92);
      border:1px solid rgba(160,197,255,.16);
      padding-left:14px;
      padding-right:14px;
    }

    .form-control::placeholder{
      color:#9fb4d8;
      opacity:1;
    }

    .form-control:focus,
    .form-select:focus{
      color:#fff;
      background:rgba(8,24,58,.98);
      border-color:rgba(78,160,255,.56);
      box-shadow:0 0 0 .18rem rgba(57,129,255,.14);
    }

    .toolbar-btn{
      border-radius:16px;
      border:1px solid rgba(160,199,255,.18);
      background:rgba(14,35,78,.88);
      color:#f7fbff !important;
      font-weight:700;
      min-height:42px;
      padding:.58rem .95rem;
      transition:all .18s ease;
      box-shadow:inset 0 1px 0 rgba(255,255,255,.03);
    }

    .toolbar-btn:hover{
      transform:translateY(-1px);
      color:#fff !important;
      background:rgba(23,48,98,.96);
      border-color:rgba(160,199,255,.24);
    }

    .btn-primary-soft{
      color:#fff !important;
      font-weight:800;
      border:none;
      border-radius:18px;
      min-height:44px;
      background:linear-gradient(135deg, #1e63ff, #3898ff);
      box-shadow:0 12px 32px rgba(37,113,255,.28);
    }

    .btn-primary-soft:hover{ color:#fff !important; transform:translateY(-1px) }

    .btn-green-soft{
      color:#fff !important;
      font-weight:800;
      border:none;
      border-radius:18px;
      min-height:44px;
      background:linear-gradient(135deg, #149f5e, #24cd7a);
      box-shadow:0 12px 32px rgba(20,159,94,.24);
    }

    .btn-green-soft:hover{ color:#fff !important; transform:translateY(-1px) }

    .btn-danger-soft{
      color:#fff !important;
      font-weight:800;
      border:none;
      border-radius:18px;
      min-height:44px;
      background:linear-gradient(135deg, #d24d65, #ff8298);
      box-shadow:0 12px 32px rgba(210,77,101,.25);
    }

    .btn-danger-soft:hover{ color:#fff !important; transform:translateY(-1px) }

    .btn-action{
      border-radius:14px;
      border:1px solid rgba(160,199,255,.16);
      background:rgba(15,37,81,.84);
      color:#fff !important;
      font-weight:700;
      min-height:38px;
      padding:.42rem .75rem;
      display:inline-flex;
      align-items:center;
      gap:8px;
      transition:all .18s ease;
    }

    .btn-action:hover{
      transform:translateY(-1px);
      background:rgba(28,53,104,.96);
      color:#fff !important;
    }

    .btn-action.detail{
      border-color:rgba(88,164,255,.22);
      box-shadow:0 8px 18px rgba(32,84,180,.12);
    }

    .btn-action.receive{
      background:rgba(18,110,68,.28);
      border-color:rgba(88,231,160,.22);
    }

    .btn-action.cancel{
      background:rgba(135,36,56,.28);
      border-color:rgba(255,143,163,.20);
    }

    .status-chip{
      display:inline-flex;
      align-items:center;
      justify-content:center;
      min-width:112px;
      padding:8px 14px;
      border-radius:999px;
      font-weight:800;
      font-size:.82rem;
      letter-spacing:.32px;
      border:1px solid transparent;
      text-transform:uppercase;
    }

    .status-draft{
      background:rgba(160,170,190,.12);
      color:#d9e2f5;
      border-color:rgba(160,170,190,.22);
    }

    .status-sent{
      background:rgba(244,190,89,.14);
      color:#ffd978;
      border-color:rgba(244,190,89,.24);
    }

    .status-received{
      background:rgba(114,240,165,.14);
      color:#7cf0ae;
      border-color:rgba(114,240,165,.24);
    }

    .status-cancelled{
      background:rgba(255,143,163,.14);
      color:#ff9cb0;
      border-color:rgba(255,143,163,.24);
    }

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

    .modal-header,
    .modal-footer{
      border-color:rgba(161,198,255,.10);
    }

    .modal-title{
      font-weight:800;
      color:#fff;
    }

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

    .nav-tabs{
      border-bottom:1px solid rgba(255,255,255,.06);
      margin-bottom:0;
      gap:8px;
    }

    .nav-tabs .nav-link{
      border:none;
      color:#cad9f2;
      font-weight:800;
      border-radius:18px 18px 0 0;
      background:transparent;
      margin-right:0;
      padding:.82rem 1.25rem;
    }

    .nav-tabs .nav-link.active{
      color:#fff;
      background:rgba(14,35,79,.98);
      border:1px solid rgba(161,198,255,.12);
      border-bottom:none;
      box-shadow:0 8px 20px rgba(0,0,0,.18);
    }

    .detail-kv{
      border:1px solid rgba(160,197,255,.10);
      background:rgba(255,255,255,.035);
      border-radius:18px;
      padding:14px 16px;
      height:100%;
    }

    .detail-kv .label{
      color:#aecdff;
      font-size:.85rem;
      font-weight:800;
      margin-bottom:6px;
      text-transform:uppercase;
      letter-spacing:.3px;
    }

    .detail-kv .value{
      color:#fff;
      font-weight:700;
      word-break:break-word;
      line-height:1.45;
    }

    .title-row{
      display:flex;
      flex-wrap:wrap;
      gap:14px;
      align-items:center;
      justify-content:space-between;
      margin-bottom:18px;
    }

    .title-left h3{
      margin:0;
      font-size:2rem;
      font-weight:800;
      color:#fff;
    }

    .title-left p{
      margin:4px 0 0;
      color:#c3d4ef;
      font-size:1rem;
    }

    .stock-box{
      border:1px solid rgba(160,197,255,.12);
      border-radius:18px;
      background:rgba(255,255,255,.04);
      padding:14px 16px;
      height:100%;
    }
    .stock-box .cap{
      color:#aecdff;
      font-size:.84rem;
      font-weight:800;
      text-transform:uppercase;
      letter-spacing:.3px;
      margin-bottom:8px;
    }
    .stock-box .qty{
      color:#fff;
      font-size:1.8rem;
      font-weight:800;
      line-height:1;
    }
    .stock-box .hint{
      color:#c7d8f4;
      font-size:.88rem;
      margin-top:8px;
    }

    @media (max-width: 991.98px){
      .brand-title{font-size:1.5rem}
      .brand-sub{font-size:.95rem}
      .kpi-value{font-size:1.75rem}
      .page-wrap{padding:18px 12px 28px}
      .panel-pad{padding:16px}
      .title-left h3{font-size:1.45rem}
      .toolbar-btn,.btn-green-soft{width:100%}
    }


/* ============================================================
   TEMA CLARO MES · Similar a Almacén
   ------------------------------------------------------------
   Mantiene el banner azul y usa tarjetas/paneles claros para
   mejorar lectura en operación.
============================================================ */
:root{
  --mes-bg:#f4f7fb;
  --mes-panel:#ffffff;
  --mes-line:#dbe6f2;
  --mes-text:#10243e;
  --mes-muted:#5e7188;
  --mes-blue:#145798;
  --mes-accent:#1f6fff;
  --mes-green:#128b4f;
  --mes-gold:#b7791f;
  --mes-danger:#c63b50;
}
html{color-scheme:light!important;}
body{
  color:var(--mes-text)!important;
  background:
    radial-gradient(circle at 12% 20%, rgba(31,111,255,.06), transparent 22%),
    linear-gradient(180deg,#f9fbff 0%,#f4f7fb 100%)!important;
}
.topbar{
  background:linear-gradient(135deg,#0b2848,#145798)!important;
  border-bottom:1px solid rgba(255,255,255,.16)!important;
  color:#fff!important;
}
.topbar *, .topbar .brand-title, .topbar .brand-sub{color:#fff!important;}
.topbar .btn, .topbar a.btn{color:#fff!important;border-color:rgba(255,255,255,.45)!important;}
.topbar .btn:hover{background:rgba(255,255,255,.14)!important;}
.glass,.kpi-card,.modal-content,.item-row,.table-wrap,.stock-box,.detail-kv{
  background:#ffffff!important;
  background-color:#ffffff!important;
  color:var(--mes-text)!important;
  border-color:var(--mes-line)!important;
  box-shadow:0 14px 34px rgba(16,36,62,.08)!important;
}
.panel-pad,.modal-body,.modal-header,.modal-footer{background:#ffffff!important;color:var(--mes-text)!important;}
.brand-title:not(.topbar .brand-title),.title-left h3,.section-title,.fw-bold,.fw-semibold,.kpi-value,.modal-title,.table thead th,.mono,label,.form-label,.detail-kv .value,.stock-box .qty{
  color:var(--mes-text)!important;
}
.brand-sub:not(.topbar .brand-sub),.muted,.small,.small-note,.kpi-label,.kpi-hint,.title-left p,.detail-kv .label,.stock-box .cap,.stock-box .hint{
  color:var(--mes-muted)!important;
}
.kpi-card{border-radius:22px;min-height:130px;}
.kpi-icon{background:#eaf2ff!important;color:#145798!important;box-shadow:none!important;}
.kpi-value{font-weight:900!important;}
.badge-soft,.small-note{
  background:#fff7e6!important;
  border:1px solid #f3d39b!important;
  color:#7c4a03!important;
}
.table{color:var(--mes-text)!important;--bs-table-bg:#fff!important;--bs-table-striped-bg:#f8fbff!important;--bs-table-hover-bg:#eef6ff!important;--bs-table-border-color:#e3ecf6!important;}
.table thead th{background:#eef4fb!important;color:var(--mes-text)!important;border-bottom:1px solid var(--mes-line)!important;}
.table tbody td{color:var(--mes-text)!important;background:#fff!important;border-color:#e7eef7!important;}
.table tbody tr:hover td{background:#f4f9ff!important;}
.form-control,.form-select,textarea,input[type="text"],input[type="number"],input[type="datetime-local"]{
  background:#fff!important;color:var(--mes-text)!important;border:1px solid #ccd9e8!important;border-radius:14px!important;box-shadow:none!important;
}
.form-control:focus,.form-select:focus,textarea:focus,input:focus{
  background:#fff!important;color:var(--mes-text)!important;border-color:rgba(31,111,255,.55)!important;box-shadow:0 0 0 .22rem rgba(31,111,255,.12)!important;
}
.form-control::placeholder{color:#8aa0b8!important;}
.form-select option{background:#fff!important;color:var(--mes-text)!important;}
.btn{border-radius:14px!important;font-weight:800!important;box-shadow:0 8px 18px rgba(16,36,62,.08)!important;}
.toolbar-btn{background:#fff!important;color:var(--mes-text)!important;border:1.5px solid #cdd9e7!important;}
.toolbar-btn:hover{background:#eef4fb!important;color:var(--mes-text)!important;}
.btn-primary-soft,.btn-green-soft,.btn-danger-soft{color:#fff!important;}
.btn-primary-soft{background:#165dff!important;}
.btn-green-soft{background:#128b4f!important;}
.btn-danger-soft{background:#c63b50!important;}
.btn-action{background:#fff!important;color:var(--mes-text)!important;border:1.5px solid #cdd9e7!important;}
.btn-action.detail{color:#0f4ecf!important;border-color:#9fc0ff!important;}
.btn-action.receive{color:#0f743f!important;border-color:#9bd8b8!important;background:#f2fff7!important;}
.btn-action.cancel{color:#b42335!important;border-color:#f0b5c0!important;background:#fff5f7!important;}
.status-draft{background:#eef2f7!important;color:#40546d!important;border-color:#d6e1ec!important;}
.status-sent{background:#fff7e6!important;color:#8a5600!important;border-color:#f3d39b!important;}
.status-received{background:#ecfdf3!important;color:#0f743f!important;border-color:#a6e7c0!important;}
.status-cancelled{background:#fff1f3!important;color:#b42335!important;border-color:#f0b5c0!important;}
.nav-tabs{border-bottom:1px solid var(--mes-line)!important;}
.nav-tabs .nav-link{color:var(--mes-muted)!important;background:#f8fbff!important;border:1px solid var(--mes-line)!important;border-radius:16px 16px 0 0!important;}
.nav-tabs .nav-link.active{color:var(--mes-text)!important;background:#fff!important;border-bottom-color:#fff!important;}
.modal-header{border-bottom:1px solid var(--mes-line)!important;}
.modal-footer{border-top:1px solid var(--mes-line)!important;}
.btn-close-white{filter:none!important;}
*::-webkit-scrollbar{width:10px;height:10px;}
*::-webkit-scrollbar-thumb{background:#c4d3e4;border-radius:999px;}
*::-webkit-scrollbar-track{background:#eef3f8;}

  </style>
</head>
<body>

<nav class="topbar">
  <div class="container-fluid py-3 px-3 px-md-4">
    <div class="d-flex flex-wrap align-items-center gap-3">
      <div>
        <h1 class="brand-title">BReport · Transferencias</h1>
        <div class="brand-sub"><?=h($companyName)?> · <?=h($locationName)?></div>
      </div>

      <div class="ms-auto d-flex flex-wrap gap-2">
        <a href="dashboard.php" class="btn toolbar-btn">
          <i class="bi bi-speedometer2 me-1"></i> Dashboard
        </a>
        <button id="btnTransferir" class="btn btn-green-soft" <?=(!$canWrite?'disabled':'')?>>
          <i class="bi bi-truck me-1"></i> Transferir
        </button>
        <button id="btnReload" class="btn toolbar-btn">
          <i class="bi bi-arrow-repeat me-1"></i> Actualizar
        </button>
        <a href="logout.php" class="btn toolbar-btn">
          <i class="bi bi-box-arrow-right me-1"></i> Salir
        </a>
      </div>
    </div>
  </div>
</nav>

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

    <div class="row g-3 mb-3">
      <div class="col-12 col-md-4">
        <div class="kpi-card panel-pad">
          <div class="kpi-top">
            <div class="kpi-icon"><i class="bi bi-box-seam"></i></div>
            <div class="kpi-label">Productos con inventario</div>
          </div>
          <div class="kpi-value mono" id="kProducts">0</div>
          <div class="kpi-hint">Artículos activos de la planta actual</div>
        </div>
      </div>

      <div class="col-12 col-md-4">
        <div class="kpi-card panel-pad">
          <div class="kpi-top">
            <div class="kpi-icon"><i class="bi bi-truck-flatbed"></i></div>
            <div class="kpi-label">En tránsito saliente</div>
          </div>
          <div class="kpi-value mono" id="kTransitOut">0.000</div>
          <div class="kpi-hint">Transferencias enviadas y aún no recibidas</div>
        </div>
      </div>

      <div class="col-12 col-md-4">
        <div class="kpi-card panel-pad">
          <div class="kpi-top">
            <div class="kpi-icon"><i class="bi bi-box-arrow-in-down"></i></div>
            <div class="kpi-label">Pendiente por recibir</div>
          </div>
          <div class="kpi-value mono" id="kPendingIn">0.000</div>
          <div class="kpi-hint">Inventario que viene en camino a esta planta</div>
        </div>
      </div>
    </div>

    <div class="glass panel-pad mb-3">
      <div class="title-row">
        <div class="title-left">
          <h3>Inventario actual</h3>
          <p>Totales por producto: total, tránsito, por recibir y disponible</p>
        </div>
        <div class="badge-soft">
          <i class="bi bi-info-circle"></i>
          Barra transferible = CF1 + CF2
        </div>
      </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">Total</th>
                <th class="text-end">En tránsito</th>
                <th class="text-end">Por recibir</th>
                <th class="text-end">Disponible</th>
                <th>UOM</th>
              </tr>
            </thead>
            <tbody id="tbInventory"></tbody>
          </table>
        </div>
      </div>
    </div>

    <ul class="nav nav-tabs">
      <li class="nav-item"><button class="nav-link active" id="tabOut" type="button">Enviadas</button></li>
      <li class="nav-item"><button class="nav-link" id="tabIn" type="button">Por recibir</button></li>
    </ul>

    <div class="glass panel-pad" style="border-top-left-radius:0;">
      <div class="title-row">
        <div class="title-left">
          <h3 id="listTitle">Transferencias enviadas</h3>
          <p>Consulta, detalle, cancelación y recepción</p>
        </div>
      </div>

      <div class="table-wrap">
        <div class="table-responsive">
          <table class="table table-hover align-middle">
            <thead>
              <tr>
                <th>ID</th>
                <th>Origen</th>
                <th>Destino</th>
                <th>Estatus</th>
                <th>Chofer</th>
                <th>Unidad</th>
                <th>Salida</th>
                <th>Llegada</th>
                <th class="text-end">Acciones</th>
              </tr>
            </thead>
            <tbody id="tbTransfers"></tbody>
          </table>
        </div>
      </div>
    </div>

  </div>
</div>

<!-- Modal nueva transferencia -->
<div class="modal fade" id="transferModal" tabindex="-1" aria-hidden="true">
  <div class="modal-dialog modal-xl modal-dialog-scrollable">
    <div class="modal-content">
      <div class="modal-header">
        <h5 class="modal-title"><i class="bi bi-truck me-2"></i>Nueva transferencia</h5>
        <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-4">
            <label class="form-label">Destino</label>
            <select id="toLoc" class="form-select"></select>
          </div>
          <div class="col-12 col-md-4">
            <label class="form-label">Chofer / conductor</label>
            <input id="driver" class="form-control" placeholder="Nombre del conductor">
          </div>
          <div class="col-12 col-md-4">
            <label class="form-label">Unidad / transporte</label>
            <input id="truck" class="form-control" placeholder="Unidad, placas, transporte">
          </div>
          <div class="col-12 col-md-4">
            <label class="form-label">Fecha / hora salida</label>
            <input id="departAt" type="datetime-local" class="form-control">
          </div>
          <div class="col-12 col-md-8">
            <label class="form-label">Notas</label>
            <input id="notes" class="form-control" placeholder="Observaciones de la transferencia">
          </div>
        </div>

        <div class="row g-3 mb-3">
          <div class="col-12 col-md-6">
            <div class="stock-box">
              <div class="cap">Disponible en origen</div>
              <div class="qty mono" id="originAvail">0.000</div>
              <div class="hint">Cantidad disponible para transferir del producto seleccionado.</div>
            </div>
          </div>
          <div class="col-12 col-md-6">
            <div class="stock-box">
              <div class="cap">Existencia actual en destino</div>
              <div class="qty mono" id="destAvail">0.000</div>
              <div class="hint">Stock actual del producto seleccionado en la ubicación destino.</div>
            </div>
          </div>
        </div>

        <div class="item-row mb-3">
          <div class="row g-3">
            <div class="col-12 col-md-4">
              <label class="form-label">Producto origen</label>
              <select id="prodSel" class="form-select"></select>
            </div>
            <div class="col-12 col-md-2">
              <label class="form-label">Cantidad</label>
              <input id="qty" class="form-control mono" value="0">
            </div>
            <div class="col-12 col-md-3">
              <label class="form-label">Conversión destino</label>
              <select id="convSel" class="form-select">
                <option value="0">Sin conversión</option>
              </select>
            </div>
            <div class="col-12 col-md-2">
              <label class="form-label">Factor</label>
              <input id="convFactor" class="form-control mono" placeholder="Ej. 4">
            </div>
            <div class="col-12 col-md-1 d-flex align-items-end">
              <button id="btnAddItem" class="btn btn-primary-soft w-100" type="button">
                <i class="bi bi-plus-lg"></i>
              </button>
            </div>
          </div>
        </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>Conversión</th>
                  <th>UOM</th>
                </tr>
              </thead>
              <tbody id="tbItems"></tbody>
            </table>
          </div>
        </div>

        <div class="small-note mt-3">
          Agrega todos los artículos antes de enviar. La transferencia quedará en tránsito hasta que la planta destino confirme la recepción.
        </div>
      </div>

      <div class="modal-footer">
        <button id="btnCreateAndSend" class="btn btn-green-soft" type="button">
          <i class="bi bi-send-check me-1"></i> Guardar y enviar
        </button>
      </div>
    </div>
  </div>
</div>

<!-- Modal recibir -->
<div class="modal fade" id="receiveModal" tabindex="-1" aria-hidden="true">
  <div class="modal-dialog">
    <div class="modal-content">
      <div class="modal-header">
        <h5 class="modal-title"><i class="bi bi-box-arrow-in-down me-2"></i>Recibir transferencia</h5>
        <button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
      </div>

      <div class="modal-body">
        <input type="hidden" id="receiveTransferId">
        <label class="form-label">Fecha / hora llegada</label>
        <input id="arriveAt" type="datetime-local" class="form-control">
      </div>

      <div class="modal-footer">
        <button id="btnConfirmReceive" class="btn btn-green-soft" type="button">
          <i class="bi bi-check2-circle me-1"></i> Confirmar recibido
        </button>
      </div>
    </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">
    <div class="modal-content">
      <div class="modal-header">
        <h5 class="modal-title"><i class="bi bi-eye me-2"></i>Detalle de transferencia</h5>
        <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" id="detailBoxes"></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>Conversión</th>
                  <th>UOM</th>
                </tr>
              </thead>
              <tbody id="detailItems"></tbody>
            </table>
          </div>
        </div>
      </div>

      <div class="modal-footer" id="detailFooterActions"></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_WRITE = <?=json_encode($canWrite)?>;
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 toLocal(dt){
  if(!dt) return '';
  dt = String(dt).replace('T',' ');
  return dt.length >= 16 ? dt.slice(0,16) : dt;
}
function statusClass(st){
  st = String(st || '').toUpperCase();
  if(st === 'DRAFT') return 'status-draft';
  if(st === 'SENT') return 'status-sent';
  if(st === 'RECEIVED') return 'status-received';
  if(st === 'CANCELLED') return 'status-cancelled';
  return 'status-draft';
}
function isBarSku(sku){
  return String(sku || '').trim().toUpperCase() === 'BARRA 150 KILOS';
}
function fmtQtyBySku(sku, val){
  return isBarSku(sku) ? String(Math.round(num(val))) : num(val).toFixed(3);
}

async function apiGet(action, params={}){
  const u = new URL(location.href);
  u.search = '';
  u.searchParams.set('action', action);
  Object.entries(params).forEach(([k,v])=>u.searchParams.set(k,v));
  const r = await fetch(u.toString(), {
    credentials:'same-origin',
    headers:{ 'X-CSRF': CSRF }
  });
  const t = await r.text();
  try { return JSON.parse(t); } catch(e){ console.error(t); 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 t = await r.text();
  try { return JSON.parse(t); } catch(e){ console.error(t); return {ok:false,error:'NO_JSON'}; }
}

let mode = 'out';
let products = [];
let destinations = [];
let conversions = [];
let workingTransferId = 0;

const transferModal = new bootstrap.Modal(document.getElementById('transferModal'));
const receiveModal  = new bootstrap.Modal(document.getElementById('receiveModal'));
const detailModal   = new bootstrap.Modal(document.getElementById('detailModal'));

async function loadCatalogs(){
  const p = await apiGet('products');
  products = p.ok ? (p.rows || []) : [];
  $('prodSel').innerHTML = products.map(x =>
    `<option value="${x.id}" data-sku="${h(x.sku)}">${h(x.sku)} · ${h(x.name)}</option>`
  ).join('');

  const d = await apiGet('destinations');
  destinations = d.ok ? (d.rows || []) : [];
  $('toLoc').innerHTML = `<option value="0">Selecciona destino</option>` +
    destinations.map(x => `<option value="${x.id}">${h(x.name)} (${h(x.code || '')})</option>`).join('');

  const c = await apiGet('product_conversions');
  conversions = c.ok ? (c.rows || []) : [];
  refreshConversionOptions();
  await refreshStockIndicators();
}

function refreshConversionOptions(){
  const productId = parseInt($('prodSel').value || '0', 10);
  const rows = conversions.filter(x => parseInt(x.from_product_id, 10) === productId);
  $('convSel').innerHTML = `<option value="0">Sin conversión</option>` + rows.map(x =>
    `<option value="${x.to_product_id}" data-factor="${x.factor}">
      ${h(x.to_sku)} · ${h(x.to_name)}
    </option>`
  ).join('');
  $('convFactor').value = '';
}

async function refreshStockIndicators(){
  const productId = parseInt($('prodSel').value || '0', 10);
  const toLocationId = parseInt($('toLoc').value || '0', 10);

  if(productId <= 0){
    $('originAvail').textContent = '0.000';
    $('destAvail').textContent = '0.000';
    return;
  }

  const selected = products.find(p => parseInt(p.id,10) === productId);
  const sku = selected?.sku || '';

  const j = await apiGet('product_stock_info', {
    product_id: productId,
    to_location_id: toLocationId
  });

  if(!j.ok){
    $('originAvail').textContent = '0.000';
    $('destAvail').textContent = '0.000';
    return;
  }

  $('originAvail').textContent = fmtQtyBySku(sku, j.origin_available);
  $('destAvail').textContent = fmtQtyBySku(sku, j.destination_available);
}

async function loadInventory(){
  const j = await apiGet('inventory_summary');
  if(!j.ok){
    alert(j.error || 'Error al cargar inventario');
    return;
  }

  const rows = j.rows || [];
  $('kProducts').textContent = String(rows.filter(r => num(r.qty_available) > 0).length);
  $('kTransitOut').textContent = rows.reduce((a,b)=>a + num(b.qty_in_transit_out), 0).toFixed(3);
  $('kPendingIn').textContent = rows.reduce((a,b)=>a + num(b.qty_pending_in), 0).toFixed(3);

  $('tbInventory').innerHTML = rows.map(r => `
    <tr>
      <td class="mono">${h(r.sku)}</td>
      <td>${h(r.name)}</td>
      <td class="text-end mono">${fmtQtyBySku(r.sku, r.qty_total)}</td>
      <td class="text-end mono">${fmtQtyBySku(r.sku, r.qty_in_transit_out)}</td>
      <td class="text-end mono">${fmtQtyBySku(r.sku, r.qty_pending_in)}</td>
      <td class="text-end mono strong-num">${fmtQtyBySku(r.sku, r.qty_available)}</td>
      <td class="mono">${h(r.uom || '')}</td>
    </tr>
  `).join('');
}

async function loadTransfers(){
  const j = await apiGet(mode === 'out' ? 'list_outbox' : 'list_inbox');
  if(!j.ok){
    alert(j.error || 'Error al cargar transferencias');
    return;
  }

  $('listTitle').textContent = mode === 'out' ? 'Transferencias enviadas' : 'Transferencias por recibir';

  $('tbTransfers').innerHTML = (j.rows || []).map(r => {
    const canReceive = mode === 'in' && r.status === 'SENT';
    const canCancel  = mode === 'out' && ['DRAFT','SENT'].includes(String(r.status || ''));

    return `
      <tr>
        <td class="mono">${h(r.id)}</td>
        <td>${h(r.from_location_name || '')}</td>
        <td>${h(r.to_location_name || '')}</td>
        <td><span class="status-chip ${statusClass(r.status)}">${h(r.status || '')}</span></td>
        <td>${h(r.driver_name || '')}</td>
        <td class="mono">${h(r.truck_no || '')}</td>
        <td class="mono">${h(toLocal(r.depart_at))}</td>
        <td class="mono">${h(toLocal(r.arrive_at))}</td>
        <td class="text-end">
          <div class="d-flex justify-content-end gap-2 flex-wrap">
            <button class="btn btn-action detail" type="button" onclick="openDetail(${Number(r.id)})">
              <i class="bi bi-eye"></i> Detalle
            </button>
            ${canReceive ? `
              <button class="btn btn-action receive" type="button" onclick="openReceive(${Number(r.id)})">
                <i class="bi bi-check2-circle"></i> Recibir
              </button>
            ` : ''}
            ${canCancel ? `
              <button class="btn btn-action cancel" type="button" onclick="cancelTransfer(${Number(r.id)})">
                <i class="bi bi-x-octagon"></i> Cancelar
              </button>
            ` : ''}
          </div>
        </td>
      </tr>
    `;
  }).join('');
}

async function createTransfer(){
  const toLoc = parseInt($('toLoc').value || '0', 10);
  if(toLoc <= 0){
    alert('Selecciona un destino');
    return false;
  }

  const j = await apiPost('create', {
    to_location_id: toLoc,
    notes: $('notes').value.trim()
  });

  if(!j.ok){
    alert(j.error || 'Error al crear transferencia');
    return false;
  }

  workingTransferId = parseInt(j.id, 10);
  return true;
}

async function addItem(){
  if(!workingTransferId){
    const ok = await createTransfer();
    if(!ok) return;
  }

  const productId = parseInt($('prodSel').value || '0', 10);
  const qty = parseFloat(String($('qty').value || '0').replace(',', '.')) || 0;
  const convTo = parseInt($('convSel').value || '0', 10);
  let convFactor = $('convFactor').value.trim();

  if(productId <= 0 || qty <= 0){
    alert('Captura producto y cantidad mayor a 0');
    return;
  }

  const info = await apiGet('product_stock_info', {
    product_id: productId,
    to_location_id: parseInt($('toLoc').value || '0', 10)
  });

  if(!info.ok){
    alert(info.error || 'No se pudo validar el disponible');
    return;
  }

  if(qty > num(info.origin_available)){
    alert('La cantidad excede el disponible del origen');
    return;
  }

  if(convTo > 0 && convFactor === ''){
    const opt = $('convSel').selectedOptions[0];
    convFactor = opt?.dataset?.factor || '';
  }

  const j = await apiPost('item_save', {
    transfer_id: workingTransferId,
    product_id: productId,
    qty: qty,
    conversion_to_product_id: convTo > 0 ? convTo : null,
    conversion_factor: convTo > 0 ? convFactor : null
  });

  if(!j.ok){
    alert(j.error || 'Error al guardar artículo');
    return;
  }

  $('qty').value = '0';
  $('convSel').value = '0';
  $('convFactor').value = '';
  await loadItems();
  await loadInventory();
  await refreshStockIndicators();
}

async function loadItems(){
  if(!workingTransferId){
    $('tbItems').innerHTML = '';
    return;
  }

  const j = await apiGet('items', {transfer_id: workingTransferId});
  if(!j.ok){
    alert(j.error || 'Error al cargar artículos');
    return;
  }

  $('tbItems').innerHTML = (j.rows || []).map(r => `
    <tr>
      <td class="mono">${h(r.sku)}</td>
      <td>${h(r.product_name)}</td>
      <td class="text-end mono">${fmtQtyBySku(r.sku, r.qty)}</td>
      <td>
        ${r.conversion_to_product_id
          ? `${h(r.conv_sku || '')} · factor ${num(r.conversion_factor).toFixed(3)}`
          : '<span class="muted">Sin conversión</span>'}
      </td>
      <td class="mono">${h(r.uom || '')}</td>
    </tr>
  `).join('');
}

async function createAndSend(){
  if(!workingTransferId){
    const ok = await createTransfer();
    if(!ok) return;
  }

  const j = await apiPost('send', {
    transfer_id: workingTransferId,
    driver_name: $('driver').value.trim(),
    truck_no: $('truck').value.trim(),
    depart_at: $('departAt').value.trim()
  });

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

  transferModal.hide();
  resetTransferModal();
  await loadInventory();
  await loadTransfers();
}

function resetTransferModal(){
  workingTransferId = 0;
  $('toLoc').value = '0';
  $('driver').value = '';
  $('truck').value = '';
  $('departAt').value = '';
  $('notes').value = '';
  $('qty').value = '0';
  $('convSel').value = '0';
  $('convFactor').value = '';
  $('tbItems').innerHTML = '';
  $('originAvail').textContent = '0.000';
  $('destAvail').textContent = '0.000';
}

function openReceive(id){
  $('receiveTransferId').value = String(id);
  $('arriveAt').value = '';
  receiveModal.show();
}

async function confirmReceive(){
  const transferId = parseInt($('receiveTransferId').value || '0', 10);
  const arriveAt = $('arriveAt').value.trim();

  if(transferId <= 0 || !arriveAt){
    alert('Captura fecha y hora de llegada');
    return;
  }

  const j = await apiPost('receive', {
    transfer_id: transferId,
    arrive_at: arriveAt
  });

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

  receiveModal.hide();
  await loadInventory();
  await loadTransfers();
}

async function cancelTransfer(id){
  if(!confirm('¿Deseas cancelar esta transferencia?')) return;

  const j = await apiPost('cancel', {transfer_id: id});
  if(!j.ok){
    alert(j.error || 'No se pudo cancelar');
    return;
  }

  await loadInventory();
  await loadTransfers();
}

async function openDetail(id){
  const j = await apiGet('detail', {transfer_id: id});
  if(!j.ok){
    alert(j.error || 'No se pudo abrir el detalle');
    return;
  }

  const t = j.transfer || {};
  const items = j.items || [];

  const boxes = [
    ['Transferencia #', t.id || ''],
    ['Estatus', `<span class="status-chip ${statusClass(t.status)}">${h(t.status || '')}</span>`],
    ['Origen', `${h(t.from_location_name || '')} ${t.from_location_code ? '(' + h(t.from_location_code) + ')' : ''}`],
    ['Destino', `${h(t.to_location_name || '')} ${t.to_location_code ? '(' + h(t.to_location_code) + ')' : ''}`],
    ['Chofer', h(t.driver_name || '') || '—'],
    ['Unidad / Transporte', h(t.truck_no || '') || '—'],
    ['Fecha salida', h(toLocal(t.depart_at) || '—')],
    ['Fecha llegada', h(toLocal(t.arrive_at) || '—')],
    ['Fecha creación', h(toLocal(t.created_at) || '—')],
    ['Notas', h(t.notes || '—')]
  ];

  $('detailBoxes').innerHTML = boxes.map(([label, value]) => `
    <div class="col-12 col-md-6 col-lg-4">
      <div class="detail-kv">
        <div class="label">${label}</div>
        <div class="value">${value}</div>
      </div>
    </div>
  `).join('');

  $('detailItems').innerHTML = items.map(r => `
    <tr>
      <td class="mono">${h(r.sku)}</td>
      <td>${h(r.product_name)}</td>
      <td class="text-end mono">${fmtQtyBySku(r.sku, r.qty)}</td>
      <td>
        ${r.conversion_to_product_id
          ? `${h(r.conv_sku || '')} · factor ${num(r.conversion_factor).toFixed(3)}`
          : '<span class="muted">Sin conversión</span>'}
      </td>
      <td class="mono">${h(r.uom || '')}</td>
    </tr>
  `).join('');

  const canCancel = mode === 'out' && ['DRAFT','SENT'].includes(String(t.status || ''));
  const canReceive = mode === 'in' && String(t.status || '') === 'SENT';

  $('detailFooterActions').innerHTML = `
    ${canCancel ? `
      <button class="btn btn-danger-soft" type="button" onclick="detailModal.hide(); cancelTransfer(${Number(t.id)})">
        <i class="bi bi-x-octagon me-1"></i> Cancelar transferencia
      </button>
    ` : ''}
    ${canReceive ? `
      <button class="btn btn-green-soft" type="button" onclick="detailModal.hide(); openReceive(${Number(t.id)})">
        <i class="bi bi-check2-circle me-1"></i> Recibir transferencia
      </button>
    ` : ''}
  `;

  detailModal.show();
}

function setMode(m){
  mode = m;
  $('tabOut').classList.toggle('active', mode === 'out');
  $('tabIn').classList.toggle('active', mode === 'in');
  $('listTitle').textContent = mode === 'out' ? 'Transferencias enviadas' : 'Transferencias por recibir';
  loadTransfers();
}

document.addEventListener('DOMContentLoaded', async () => {
  await loadCatalogs();
  await loadInventory();
  await loadTransfers();

  $('btnReload').addEventListener('click', async () => {
    await loadInventory();
    await loadTransfers();
  });

  $('btnTransferir').addEventListener('click', async () => {
    resetTransferModal();
    transferModal.show();
    await refreshStockIndicators();
  });

  $('prodSel').addEventListener('change', async () => {
    refreshConversionOptions();
    await refreshStockIndicators();
  });

  $('toLoc').addEventListener('change', refreshStockIndicators);

  $('convSel').addEventListener('change', () => {
    const opt = $('convSel').selectedOptions[0];
    $('convFactor').value = opt?.dataset?.factor || '';
  });

  $('btnAddItem').addEventListener('click', addItem);
  $('btnCreateAndSend').addEventListener('click', createAndSend);
  $('btnConfirmReceive').addEventListener('click', confirmReceive);

  $('tabOut').addEventListener('click', () => setMode('out'));
  $('tabIn').addEventListener('click', () => setMode('in'));
});
</script>
</body>
</html>