<?php
/**
 * HieloBolsa/produccion.php
 * ============================================================
 * BReport · Hielo Bolsa / Empaque (v3.0)
 * ------------------------------------------------------------
 * OBJETIVO:
 * - Flujo por turno con artículo activo
 * - Cambio de producto con envío obligatorio a cuarentena
 * - Configuración por planta: zona horaria, objetivo por turno, uso PLC
 * - Selección de TAG PLC desde configuración
 * - Reporte manual de tarima
 * - Reporte automático por PLC usando UPDATE sobre fila fija de pack_plc_signals
 * - Generación de etiqueta PDF por tarima
 *
 * MODELO PLC:
 * - pack_plc_signals se usa como catálogo/estado actual de señales
 * - Una fila por TAG
 * - El PLC/middleware cambia signal_state a 1 por UPDATE
 * - El sistema procesa la señal y la regresa a 0
 * ============================================================
 */

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'];
$uid        = (int)($u['id'] ?? 0);
$action     = (string)($_GET['action'] ?? '');

const PACK_QA_CODE = 'QA';
const PACK_PT_CODE = 'PT';
const PACK_DEFAULT_TZ = 'America/Monterrey';

/* ===================== Helpers base ===================== */
function jexit($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') return;
  $hdr = (string)($_SERVER['HTTP_X_CSRF'] ?? '');
  if ($hdr === '') jexit(['ok'=>false,'error'=>'Falta X-CSRF'], 419);
  csrf_verify($hdr);
}
function 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 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 idx_exists(PDO $pdo, string $table, string $idx): bool {
  $st = $pdo->prepare("SELECT COUNT(*) FROM information_schema.statistics WHERE table_schema = DATABASE() AND table_name=? AND index_name=?");
  $st->execute([$table, $idx]);
  return (int)$st->fetchColumn() > 0;
}
function norm_shift(int $shift): int {
  return in_array($shift, [1,2,3], true) ? $shift : 1;
}
function shift_label(int $shift): string {
  return match($shift) {
    1 => 'Turno 1',
    2 => 'Turno 2',
    3 => 'Turno 3',
    default => 'Turno 1'
  };
}
function pdf_escape(string $text): string {
  return str_replace(['\\', '(', ')'], ['\\\\', '\\(', '\\)'], $text);
}
function normalize_tz(string $tz): string {
  $tz = trim($tz);
  if ($tz === '') return PACK_DEFAULT_TZ;
  try {
    new DateTimeZone($tz);
    return $tz;
  } catch (Throwable $e) {
    return PACK_DEFAULT_TZ;
  }
}
function tz_today(string $tz): string {
  return (new DateTimeImmutable('now', new DateTimeZone($tz)))->format('Y-m-d');
}
function tz_now(string $tz): string {
  return (new DateTimeImmutable('now', new DateTimeZone($tz)))->format('Y-m-d H:i:s');
}
function combine_local_now_date(string $date, string $tz): string {
  return (new DateTimeImmutable('now', new DateTimeZone($tz)))->format('Y-m-d H:i:s');
}
function wh_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 code=? AND active=1 LIMIT 1");
  $st->execute([$companyId,$locationId,$code]);
  return (int)($st->fetchColumn() ?: 0);
}
function get_running_shift(PDO $pdo, int $companyId, int $locationId, int $userId, string $date): ?array {
  $st = $pdo->prepare("
    SELECT id, work_date, shift, start_at, end_at, status, last_heartbeat_at
    FROM shift_sessions
    WHERE company_id=? AND location_id=? AND user_id=? AND work_date=? AND status='RUNNING'
    ORDER BY id DESC LIMIT 1
  ");
  $st->execute([$companyId,$locationId,$userId,$date]);
  $r = $st->fetch(PDO::FETCH_ASSOC);
  return $r ?: null;
}

/* ===================== Setup de tablas ===================== */
function ensure_pack_schema(PDO $pdo): void {
  $pdo->exec("
    CREATE TABLE IF NOT EXISTS pack_product_specs (
      id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
      company_id BIGINT UNSIGNED NOT NULL,
      location_id BIGINT UNSIGNED NOT NULL,
      product_id BIGINT UNSIGNED NOT NULL,
      pieces_per_layer INT NOT NULL DEFAULT 0,
      layers_per_pallet INT NOT NULL DEFAULT 0,
      pieces_per_pallet INT NOT NULL DEFAULT 0,
      dlx_code VARCHAR(30) NOT NULL DEFAULT '',
      active TINYINT(1) NOT NULL DEFAULT 1,
      created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
      updated_at DATETIME DEFAULT NULL,
      PRIMARY KEY (id),
      UNIQUE KEY uq_pack_product_specs_scope (company_id, location_id, product_id),
      KEY idx_pack_product_specs_scope (company_id, location_id, active)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci
  ");

  $pdo->exec("
    CREATE TABLE IF NOT EXISTS pack_runtime_settings (
      id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
      company_id BIGINT UNSIGNED NOT NULL,
      location_id BIGINT UNSIGNED NOT NULL,
      timezone VARCHAR(80) NOT NULL DEFAULT 'America/Monterrey',
      shift_target_pallets INT NOT NULL DEFAULT 0,
      plc_enabled TINYINT(1) NOT NULL DEFAULT 0,
      plc_signal_name VARCHAR(60) NOT NULL DEFAULT 'PACK_FINISHED',
      updated_by BIGINT UNSIGNED DEFAULT NULL,
      created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
      updated_at DATETIME DEFAULT NULL,
      PRIMARY KEY (id),
      UNIQUE KEY uq_pack_runtime_settings_scope (company_id, location_id)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci
  ");

  if (!col_exists($pdo, 'pack_runtime_settings', 'plc_tag_id')) {
    $pdo->exec("ALTER TABLE pack_runtime_settings ADD COLUMN plc_tag_id BIGINT UNSIGNED DEFAULT NULL AFTER plc_signal_name");
  }

  $pdo->exec("
    CREATE TABLE IF NOT EXISTS pack_shift_runtime (
      id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
      company_id BIGINT UNSIGNED NOT NULL,
      location_id BIGINT UNSIGNED NOT NULL,
      work_date DATE NOT NULL,
      shift TINYINT UNSIGNED NOT NULL,
      shift_session_id BIGINT UNSIGNED DEFAULT NULL,
      active_product_id BIGINT UNSIGNED DEFAULT NULL,
      active_since DATETIME DEFAULT NULL,
      last_report_at DATETIME DEFAULT NULL,
      status VARCHAR(20) NOT NULL DEFAULT 'RUNNING',
      created_by BIGINT UNSIGNED NOT NULL,
      created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
      updated_by BIGINT UNSIGNED DEFAULT NULL,
      updated_at DATETIME DEFAULT NULL,
      PRIMARY KEY (id),
      UNIQUE KEY uq_pack_shift_runtime_scope (company_id, location_id, work_date, shift),
      KEY idx_pack_shift_runtime_active (company_id, location_id, active_product_id)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci
  ");

  $pdo->exec("
    CREATE TABLE IF NOT EXISTS pack_finished_pallets (
      id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
      company_id BIGINT UNSIGNED NOT NULL,
      location_id BIGINT UNSIGNED NOT NULL,
      work_date DATE NOT NULL,
      shift TINYINT UNSIGNED NOT NULL,
      shift_session_id BIGINT UNSIGNED DEFAULT NULL,
      pallet_folio VARCHAR(50) NOT NULL,
      serial_no VARCHAR(80) NOT NULL,
      label_code VARCHAR(120) NOT NULL,
      product_id BIGINT UNSIGNED NOT NULL,
      warehouse_id BIGINT UNSIGNED NOT NULL,
      entry_time DATETIME NOT NULL,
      pallet_qty INT NOT NULL DEFAULT 1,
      pieces_per_pallet INT NOT NULL DEFAULT 0,
      total_pieces INT NOT NULL DEFAULT 0,
      source_mode VARCHAR(20) NOT NULL DEFAULT 'MANUAL',
      plc_signal_id BIGINT UNSIGNED DEFAULT NULL,
      label_pdf_path VARCHAR(255) DEFAULT NULL,
      status VARCHAR(20) NOT NULL DEFAULT 'POSTED',
      notes VARCHAR(255) DEFAULT NULL,
      created_by BIGINT UNSIGNED NOT NULL,
      created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
      PRIMARY KEY (id),
      UNIQUE KEY uq_pack_finished_folio (company_id, location_id, pallet_folio),
      UNIQUE KEY uq_pack_finished_serial (company_id, location_id, serial_no),
      KEY idx_pack_finished_main (company_id, location_id, work_date, shift, product_id)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci
  ");

  $pdo->exec("
    CREATE TABLE IF NOT EXISTS pack_changeovers (
      id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
      company_id BIGINT UNSIGNED NOT NULL,
      location_id BIGINT UNSIGNED NOT NULL,
      work_date DATE NOT NULL,
      shift TINYINT UNSIGNED NOT NULL,
      from_product_id BIGINT UNSIGNED NOT NULL,
      to_product_id BIGINT UNSIGNED NOT NULL,
      qa_warehouse_id BIGINT UNSIGNED NOT NULL,
      qa_qty INT NOT NULL DEFAULT 0,
      changed_at DATETIME NOT NULL,
      notes VARCHAR(255) DEFAULT NULL,
      created_by BIGINT UNSIGNED NOT NULL,
      created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
      PRIMARY KEY (id),
      KEY idx_pack_changeovers_scope (company_id, location_id, work_date, shift)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci
  ");

  $pdo->exec("
    CREATE TABLE IF NOT EXISTS pack_quarantine_entries (
      id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
      company_id BIGINT UNSIGNED NOT NULL,
      location_id BIGINT UNSIGNED NOT NULL,
      work_date DATE NOT NULL,
      shift TINYINT UNSIGNED NOT NULL,
      product_id BIGINT UNSIGNED NOT NULL,
      warehouse_id BIGINT UNSIGNED NOT NULL,
      qty INT NOT NULL DEFAULT 0,
      reason VARCHAR(40) NOT NULL DEFAULT 'CHANGEOVER',
      source_changeover_id BIGINT UNSIGNED DEFAULT NULL,
      entry_time DATETIME NOT NULL,
      status VARCHAR(20) NOT NULL DEFAULT 'AVAILABLE',
      notes VARCHAR(255) DEFAULT NULL,
      created_by BIGINT UNSIGNED NOT NULL,
      created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
      PRIMARY KEY (id),
      KEY idx_pack_quarantine_scope (company_id, location_id, warehouse_id, product_id, status)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci
  ");

  $pdo->exec("
    CREATE TABLE IF NOT EXISTS pack_plc_signals (
      id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
      company_id BIGINT UNSIGNED NOT NULL,
      location_id BIGINT UNSIGNED NOT NULL,
      signal_name VARCHAR(60) NOT NULL DEFAULT 'PACK_FINISHED',
      source_code VARCHAR(60) DEFAULT NULL,
      product_id BIGINT UNSIGNED DEFAULT NULL,
      signal_state TINYINT(1) NOT NULL DEFAULT 0,
      payload_json JSON DEFAULT NULL,
      triggered_at DATETIME DEFAULT NULL,
      consumed_at DATETIME DEFAULT NULL,
      status VARCHAR(20) NOT NULL DEFAULT 'IDLE',
      created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
      updated_at DATETIME DEFAULT NULL,
      PRIMARY KEY (id),
      KEY idx_pack_plc_scope (company_id, location_id, signal_name, signal_state, status)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci
  ");

  if (!col_exists($pdo, 'pack_plc_signals', 'plc_name')) {
    $pdo->exec("ALTER TABLE pack_plc_signals ADD COLUMN plc_name VARCHAR(80) DEFAULT NULL AFTER location_id");
  }
  if (!col_exists($pdo, 'pack_plc_signals', 'ip_address')) {
    $pdo->exec("ALTER TABLE pack_plc_signals ADD COLUMN ip_address VARCHAR(60) DEFAULT NULL AFTER plc_name");
  }
  if (!col_exists($pdo, 'pack_plc_signals', 'tag_name')) {
    $pdo->exec("ALTER TABLE pack_plc_signals ADD COLUMN tag_name VARCHAR(80) DEFAULT NULL AFTER ip_address");
  }
  if (!col_exists($pdo, 'pack_plc_signals', 'enabled')) {
    $pdo->exec("ALTER TABLE pack_plc_signals ADD COLUMN enabled TINYINT(1) NOT NULL DEFAULT 1 AFTER product_id");
  }
  if (!col_exists($pdo, 'pack_plc_signals', 'print_enabled')) {
    $pdo->exec("ALTER TABLE pack_plc_signals ADD COLUMN print_enabled TINYINT(1) NOT NULL DEFAULT 1 AFTER enabled");
  }
  if (!col_exists($pdo, 'pack_plc_signals', 'last_value_at')) {
    $pdo->exec("ALTER TABLE pack_plc_signals ADD COLUMN last_value_at DATETIME DEFAULT NULL AFTER triggered_at");
  }
  if (!idx_exists($pdo, 'pack_plc_signals', 'uq_pack_plc_tag')) {
    try {
      $pdo->exec("ALTER TABLE pack_plc_signals ADD UNIQUE KEY uq_pack_plc_tag (company_id, location_id, tag_name)");
    } catch (Throwable $e) {
      // Ignorar si hay datos duplicados existentes
    }
  }

  $pdo->exec("
    CREATE TABLE IF NOT EXISTS pack_serial_registry (
      id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
      company_id BIGINT UNSIGNED NOT NULL,
      location_id BIGINT UNSIGNED NOT NULL,
      product_id BIGINT UNSIGNED NOT NULL,
      serial_no VARCHAR(80) NOT NULL,
      pallet_id BIGINT UNSIGNED DEFAULT NULL,
      created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
      PRIMARY KEY (id),
      UNIQUE KEY uq_pack_serial_registry (company_id, location_id, serial_no),
      KEY idx_pack_serial_product (company_id, location_id, product_id)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci
  ");
}

function seed_pack_specs_if_empty(PDO $pdo, int $companyId, int $locationId): void {
  $st = $pdo->prepare("SELECT COUNT(*) FROM pack_product_specs WHERE company_id=? AND location_id=?");
  $st->execute([$companyId,$locationId]);
  if ((int)$st->fetchColumn() > 0) return;

  $seed = [
    'HB-2.5KG' => [15,15,225,'6448'],
    'HB-5KG-BLANQUITA' => [10,14,140,'6449'],
    'HB-5KG-SEVEN' => [10,14,140,'8589'],
    'HB-10KG' => [6,10,60,'6450'],
    'HB-20KG-CONSUMO' => [4,8,35,'6451'],
    'HB-20KG-INDUSTRIAL' => [4,8,35,'6452'],
  ];

  foreach ($seed as $sku => $cfg) {
    $st = $pdo->prepare("SELECT id FROM products WHERE company_id=? AND sku=? LIMIT 1");
    $st->execute([$companyId,$sku]);
    $p = $st->fetch(PDO::FETCH_ASSOC);
    if (!$p) continue;

    $ins = $pdo->prepare("
      INSERT INTO pack_product_specs (company_id, location_id, product_id, pieces_per_layer, layers_per_pallet, pieces_per_pallet, dlx_code, active, created_at)
      VALUES (?, ?, ?, ?, ?, ?, ?, 1, NOW())
    ");
    $ins->execute([$companyId,$locationId,(int)$p['id'],$cfg[0],$cfg[1],$cfg[2],$cfg[3]]);
  }
}

function ensure_runtime_settings(PDO $pdo, int $companyId, int $locationId, int $uid): void {
  $st = $pdo->prepare("SELECT COUNT(*) FROM pack_runtime_settings WHERE company_id=? AND location_id=?");
  $st->execute([$companyId,$locationId]);
  if ((int)$st->fetchColumn() > 0) return;

  $defaultTarget = 400;

  $ins = $pdo->prepare("
    INSERT INTO pack_runtime_settings (company_id, location_id, timezone, shift_target_pallets, plc_enabled, plc_signal_name, plc_tag_id, updated_by, created_at)
    VALUES (?, ?, ?, ?, 0, 'PACK_FINISHED', NULL, ?, NOW())
  ");
  $ins->execute([$companyId,$locationId,PACK_DEFAULT_TZ,$defaultTarget,$uid]);
}

function get_runtime_settings(PDO $pdo, int $companyId, int $locationId): array {
  $st = $pdo->prepare("SELECT * FROM pack_runtime_settings WHERE company_id=? AND location_id=? LIMIT 1");
  $st->execute([$companyId,$locationId]);
  $r = $st->fetch(PDO::FETCH_ASSOC);
  return $r ?: [
    'timezone' => PACK_DEFAULT_TZ,
    'shift_target_pallets' => 0,
    'plc_enabled' => 0,
    'plc_signal_name' => 'PACK_FINISHED',
    'plc_tag_id' => null,
  ];
}

function ensure_shift_runtime(PDO $pdo, int $companyId, int $locationId, string $date, int $shift, ?int $shiftSessionId, int $uid): void {
  $st = $pdo->prepare("SELECT id FROM pack_shift_runtime WHERE company_id=? AND location_id=? AND work_date=? AND shift=? LIMIT 1");
  $st->execute([$companyId,$locationId,$date,$shift]);
  if ($st->fetchColumn()) return;

  $ins = $pdo->prepare("
    INSERT INTO pack_shift_runtime (company_id, location_id, work_date, shift, shift_session_id, status, created_by, created_at)
    VALUES (?, ?, ?, ?, ?, 'RUNNING', ?, NOW())
  ");
  $ins->execute([$companyId,$locationId,$date,$shift,$shiftSessionId,$uid]);
}

function get_shift_runtime(PDO $pdo, int $companyId, int $locationId, string $date, int $shift): ?array {
  $st = $pdo->prepare("SELECT * FROM pack_shift_runtime WHERE company_id=? AND location_id=? AND work_date=? AND shift=? LIMIT 1");
  $st->execute([$companyId,$locationId,$date,$shift]);
  $r = $st->fetch(PDO::FETCH_ASSOC);
  return $r ?: null;
}

function get_plc_tags(PDO $pdo, int $companyId, int $locationId): array {
  $st = $pdo->prepare("
    SELECT
      id,
      plc_name,
      ip_address,
      tag_name,
      signal_name,
      product_id,
      enabled,
      print_enabled,
      signal_state,
      status,
      triggered_at,
      consumed_at,
      last_value_at,
      updated_at
    FROM pack_plc_signals
    WHERE company_id=? AND location_id=?
    ORDER BY plc_name, tag_name, id
  ");
  $st->execute([$companyId,$locationId]);
  return $st->fetchAll(PDO::FETCH_ASSOC);
}

function get_selected_plc_tag(PDO $pdo, int $companyId, int $locationId, ?int $tagId): ?array {
  if (!$tagId || $tagId <= 0) return null;
  $st = $pdo->prepare("
    SELECT *
    FROM pack_plc_signals
    WHERE id=? AND company_id=? AND location_id=? AND enabled=1
    LIMIT 1
  ");
  $st->execute([$tagId,$companyId,$locationId]);
  $r = $st->fetch(PDO::FETCH_ASSOC);
  return $r ?: null;
}

/* ===================== Serial / PDF ===================== */
function next_serial(PDO $pdo, int $companyId, int $locationId, int $productId, string $date): string {
  $prefix = 'T' . str_replace('-', '', $date) . '-';
  $st = $pdo->prepare("
    SELECT serial_no
    FROM pack_serial_registry
    WHERE company_id=? AND location_id=? AND serial_no LIKE ?
    ORDER BY id DESC LIMIT 1
  ");
  $st->execute([$companyId,$locationId,$prefix . '%']);
  $last = (string)($st->fetchColumn() ?: '');
  $n = 1;
  if ($last !== '' && preg_match('/-(\\d{5})$/', $last, $m)) {
    $n = ((int)$m[1]) + 1;
  }
  return $prefix . str_pad((string)$n, 5, '0', STR_PAD_LEFT);
}

function next_pallet_folio(PDO $pdo, int $companyId, int $locationId, string $date): string {
  $prefix = 'PK-' . str_replace('-', '', $date) . '-';
  $st = $pdo->prepare("
    SELECT pallet_folio
    FROM pack_finished_pallets
    WHERE company_id=? AND location_id=? AND work_date=? AND pallet_folio LIKE ?
    ORDER BY id DESC LIMIT 1
  ");
  $st->execute([$companyId,$locationId,$date,$prefix . '%']);
  $last = (string)($st->fetchColumn() ?: '');
  $n = 1;
  if ($last !== '' && preg_match('/-(\\d{4})$/', $last, $m)) {
    $n = ((int)$m[1]) + 1;
  }
  return $prefix . str_pad((string)$n, 4, '0', STR_PAD_LEFT);
}

function make_label_pdf(string $outFile, array $label): void {
  $title = pdf_escape((string)($label['title'] ?? 'HIELO BLANQUITA'));
  $company = pdf_escape((string)($label['company'] ?? 'Empresa: Hielo Bolsa'));
  $product = pdf_escape((string)($label['product_name'] ?? ''));
  $description = pdf_escape((string)($label['description'] ?? ''));
  $qty = pdf_escape((string)($label['pieces_per_pallet'] ?? '0'));
  $serial = pdf_escape((string)($label['serial_no'] ?? ''));
  $date = pdf_escape((string)($label['entry_time'] ?? ''));

  $content = "BT
/F1 20 Tf
30 392 Td
($title) Tj

0 -24 Td
/F2 12 Tf
($company) Tj

0 -34 Td
/F1 14 Tf
(Articulo:) Tj
0 -18 Td
/F2 15 Tf
($product) Tj

0 -30 Td
/F1 14 Tf
(Descripcion:) Tj
0 -18 Td
/F2 12 Tf
($description) Tj

0 -30 Td
/F1 14 Tf
(Cantidad por tarima:) Tj
0 -20 Td
/F1 24 Tf
($qty) Tj

0 -34 Td
/F1 14 Tf
(Serie:) Tj

0 -24 Td
/F1 30 Tf
($serial) Tj

0 -34 Td
/F2 10 Tf
(Fecha de reporte: $date) Tj
ET";

  $objects = [];
  $objects[] = "<< /Type /Catalog /Pages 2 0 R >>";
  $objects[] = "<< /Type /Pages /Kids [3 0 R] /Count 1 >>";
  $objects[] = "<< /Type /Page /Parent 2 0 R /MediaBox [0 0 288 432] /Resources << /Font << /F1 4 0 R /F2 5 0 R >> >> /Contents 6 0 R >>";
  $objects[] = "<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica-Bold >>";
  $objects[] = "<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>";
  $objects[] = "<< /Length " . strlen($content) . " >>\nstream\n" . $content . "\nendstream";

  $pdf = "%PDF-1.4\n";
  $offsets = [0];
  foreach ($objects as $i => $obj) {
    $offsets[] = strlen($pdf);
    $pdf .= ($i + 1) . " 0 obj\n" . $obj . "\nendobj\n";
  }
  $xref = strlen($pdf);
  $pdf .= "xref\n0 " . (count($objects) + 1) . "\n";
  $pdf .= "0000000000 65535 f \n";
  for ($i = 1; $i <= count($objects); $i++) {
    $pdf .= str_pad((string)$offsets[$i], 10, '0', STR_PAD_LEFT) . " 00000 n \n";
  }
  $pdf .= "trailer << /Size " . (count($objects) + 1) . " /Root 1 0 R >>\nstartxref\n" . $xref . "\n%%EOF";

  file_put_contents($outFile, $pdf);
}

function ensure_label_dir(string $baseDir): string {
  $dir = $baseDir . '/labels';
  if (!is_dir($dir)) @mkdir($dir, 0777, true);
  return $dir;
}

/* ===================== Registro de producción ===================== */
function insert_production_entry(
  PDO $pdo,
  int $companyId,
  int $locationId,
  int $productId,
  int $warehouseId,
  int $qty,
  string $entryTime,
  string $workDate,
  int $shift,
  int $uid
): void {
  $cols = [
    'company_id',
    'location_id',
    'product_id',
    'warehouse_id',
    'qty',
    'entry_time',
    'created_by',
    'created_at'
  ];

  $vals = [
    $companyId,
    $locationId,
    $productId,
    $warehouseId,
    $qty,
    $entryTime,
    $uid,
    date('Y-m-d H:i:s')
  ];

  if (col_exists($pdo, 'production_entries', 'capture_uid')) {
    $cols[] = 'capture_uid';
    $vals[] = null;
  }
  if (col_exists($pdo, 'production_entries', 'produced_at')) {
    $cols[] = 'produced_at';
    $vals[] = $entryTime;
  }
  if (col_exists($pdo, 'production_entries', 'work_date')) {
    $cols[] = 'work_date';
    $vals[] = $workDate;
  }
  if (col_exists($pdo, 'production_entries', 'shift')) {
    $cols[] = 'shift';
    $vals[] = $shift;
  }

  $ph = implode(',', array_fill(0, count($cols), '?'));
  $sql = "INSERT INTO production_entries (" . implode(',', $cols) . ") VALUES ($ph)";
  $st = $pdo->prepare($sql);
  $st->execute($vals);
}

function get_active_product_stats(PDO $pdo, int $companyId, int $locationId, string $date, int $shift, int $productId): array {
  $st = $pdo->prepare("
    SELECT COUNT(*) AS pallets, COALESCE(SUM(total_pieces),0) AS pieces
    FROM pack_finished_pallets
    WHERE company_id=? AND location_id=? AND work_date=? AND shift=? AND product_id=?
  ");
  $st->execute([$companyId,$locationId,$date,$shift,$productId]);
  $r = $st->fetch(PDO::FETCH_ASSOC) ?: [];
  return [
    'pallets' => (int)($r['pallets'] ?? 0),
    'pieces'  => (int)($r['pieces'] ?? 0),
  ];
}

function get_pack_items(PDO $pdo, int $companyId, int $locationId): array {
  $st = $pdo->prepare("
    SELECT p.id AS product_id, p.sku, p.name, p.uom, p.erp_id,
           s.pieces_per_layer, s.layers_per_pallet, s.pieces_per_pallet, s.dlx_code
    FROM pack_product_specs s
    JOIN products p ON p.id = s.product_id
    WHERE s.company_id=? AND s.location_id=? AND s.active=1 AND p.active=1
    ORDER BY p.name
  ");
  $st->execute([$companyId,$locationId]);
  return $st->fetchAll(PDO::FETCH_ASSOC);
}

/* ===================== Bootstrap de esquema ===================== */
ensure_pack_schema($pdo);
seed_pack_specs_if_empty($pdo, $companyId, $locationId);
ensure_runtime_settings($pdo, $companyId, $locationId, $uid);
$settings = get_runtime_settings($pdo, $companyId, $locationId);
$APP_TZ = normalize_tz((string)($settings['timezone'] ?? PACK_DEFAULT_TZ));

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

    if ($action === 'setup') {
      $items = get_pack_items($pdo, $companyId, $locationId);
      $ptId = wh_id_by_code($pdo, $companyId, $locationId, PACK_PT_CODE);
      $qaId = wh_id_by_code($pdo, $companyId, $locationId, PACK_QA_CODE);
      $plcTags = get_plc_tags($pdo, $companyId, $locationId);

      jexit([
        'ok' => true,
        'items' => $items,
        'settings' => $settings,
        'plc_tags' => $plcTags,
        'pt_warehouse_id' => $ptId,
        'qa_warehouse_id' => $qaId,
        'today' => tz_today($APP_TZ),
        'timezone' => $APP_TZ,
      ]);
    }

    if ($action === 'save_settings') {
      $d = read_json();
      $tz = normalize_tz((string)($d['timezone'] ?? PACK_DEFAULT_TZ));
      $target = max(0, (int)($d['shift_target_pallets'] ?? 0));
      $plcEnabled = !empty($d['plc_enabled']) ? 1 : 0;
      $plcTagId = (int)($d['plc_tag_id'] ?? 0);

      $selectedTag = null;
      $signalName = 'PACK_FINISHED';

      if ($plcTagId > 0) {
        $selectedTag = get_selected_plc_tag($pdo, $companyId, $locationId, $plcTagId);
        if (!$selectedTag) {
          jexit(['ok'=>false,'error'=>'El TAG PLC seleccionado no existe o está deshabilitado.'], 400);
        }
        $signalName = (string)($selectedTag['signal_name'] ?: 'PACK_FINISHED');
      }

      $st = $pdo->prepare("
        UPDATE pack_runtime_settings
        SET timezone=?, shift_target_pallets=?, plc_enabled=?, plc_signal_name=?, plc_tag_id=?, updated_by=?, updated_at=NOW()
        WHERE company_id=? AND location_id=?
      ");
      $st->execute([$tz,$target,$plcEnabled,$signalName,($plcTagId > 0 ? $plcTagId : null),$uid,$companyId,$locationId]);

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

    if ($action === 'shift_start') {
      $d = read_json();
      $date = (string)($d['date'] ?? tz_today($APP_TZ));
      $shift = norm_shift((int)($d['shift'] ?? 1));

      $running = get_running_shift($pdo,$companyId,$locationId,$uid,$date);
      if ($running) {
        ensure_shift_runtime($pdo,$companyId,$locationId,$date,(int)$running['shift'],(int)$running['id'],$uid);
        jexit(['ok'=>true,'running'=>$running,'already'=>true]);
      }

      $st = $pdo->prepare("
        INSERT INTO shift_sessions (company_id, location_id, user_id, work_date, shift, start_at, status, last_heartbeat_at)
        VALUES (?, ?, ?, ?, ?, NOW(), 'RUNNING', NOW())
      ");
      $st->execute([$companyId,$locationId,$uid,$date,$shift]);
      $shiftId = (int)$pdo->lastInsertId();

      ensure_shift_runtime($pdo,$companyId,$locationId,$date,$shift,$shiftId,$uid);
      $running = get_running_shift($pdo,$companyId,$locationId,$uid,$date);

      jexit(['ok'=>true,'running'=>$running,'already'=>false]);
    }

    if ($action === 'shift_end') {
      $d = read_json();
      $date = (string)($d['date'] ?? tz_today($APP_TZ));
      $running = get_running_shift($pdo,$companyId,$locationId,$uid,$date);
      if (!$running) jexit(['ok'=>false,'error'=>'No hay turno activo'], 409);

      $pdo->beginTransaction();

      $st = $pdo->prepare("
        UPDATE shift_sessions
        SET status='ENDED', end_at=NOW(), last_heartbeat_at=NOW()
        WHERE id=? AND company_id=? AND location_id=? AND user_id=? AND status='RUNNING'
      ");
      $st->execute([(int)$running['id'],$companyId,$locationId,$uid]);

      $st = $pdo->prepare("
        UPDATE pack_shift_runtime
        SET status='ENDED', active_product_id=NULL, updated_by=?, updated_at=NOW()
        WHERE company_id=? AND location_id=? AND work_date=? AND shift=?
      ");
      $st->execute([$uid,$companyId,$locationId,$date,(int)$running['shift']]);

      $pdo->commit();
      jexit(['ok'=>true]);
    }

    if ($action === 'day_load') {
      $date = (string)($_GET['date'] ?? tz_today($APP_TZ));
      $running = get_running_shift($pdo,$companyId,$locationId,$uid,$date);
      $runtime = null;
      $activeProduct = null;
      $activeStats = ['pallets'=>0,'pieces'=>0];
      $plcTags = get_plc_tags($pdo, $companyId, $locationId);
      $selectedPlcTag = null;

      if ($running) {
        ensure_shift_runtime($pdo,$companyId,$locationId,$date,(int)$running['shift'],(int)$running['id'],$uid);
        $runtime = get_shift_runtime($pdo,$companyId,$locationId,$date,(int)$running['shift']);

        if (!empty($runtime['active_product_id'])) {
          $st = $pdo->prepare("
            SELECT p.id AS product_id, p.sku, p.name, p.uom, p.erp_id,
                   s.pieces_per_layer, s.layers_per_pallet, s.pieces_per_pallet, s.dlx_code
            FROM pack_product_specs s
            JOIN products p ON p.id=s.product_id
            WHERE s.company_id=? AND s.location_id=? AND p.id=? LIMIT 1
          ");
          $st->execute([$companyId,$locationId,(int)$runtime['active_product_id']]);
          $activeProduct = $st->fetch(PDO::FETCH_ASSOC) ?: null;

          if ($activeProduct) {
            $activeStats = get_active_product_stats($pdo,$companyId,$locationId,$date,(int)$running['shift'],(int)$activeProduct['product_id']);
          }
        }
      }

      if (!empty($settings['plc_tag_id'])) {
        $selectedPlcTag = get_selected_plc_tag($pdo, $companyId, $locationId, (int)$settings['plc_tag_id']);
      }

      $histSql = "
        SELECT pf.id, pf.entry_time, pf.shift, pf.pallet_folio, pf.serial_no, pf.label_code,
               pf.pieces_per_pallet, pf.total_pieces, pf.source_mode, pf.plc_signal_id,
               p.name AS product_name, p.sku, pf.label_pdf_path
        FROM pack_finished_pallets pf
        JOIN products p ON p.id = pf.product_id
        WHERE pf.company_id=? AND pf.location_id=? AND pf.work_date=?
      ";
      $params = [$companyId,$locationId,$date];
      if ($running) {
        $histSql .= " AND pf.shift=?";
        $params[] = (int)$running['shift'];
      }
      $histSql .= " ORDER BY pf.id DESC LIMIT 200";

      $st = $pdo->prepare($histSql);
      $st->execute($params);
      $history = $st->fetchAll(PDO::FETCH_ASSOC);

      $qaQty = 0;
      if ($running) {
        $st = $pdo->prepare("
          SELECT COALESCE(SUM(qty),0)
          FROM pack_quarantine_entries
          WHERE company_id=? AND location_id=? AND work_date=? AND shift=? AND status='AVAILABLE'
        ");
        $st->execute([$companyId,$locationId,$date,(int)$running['shift']]);
        $qaQty = (int)$st->fetchColumn();
      }

      $pendingPlc = 0;
      if (!empty($settings['plc_enabled']) && !empty($settings['plc_tag_id'])) {
        $st = $pdo->prepare("
          SELECT COUNT(*)
          FROM pack_plc_signals
          WHERE id=? AND company_id=? AND location_id=? AND enabled=1 AND signal_state=1
        ");
        $st->execute([(int)$settings['plc_tag_id'],$companyId,$locationId]);
        $pendingPlc = (int)$st->fetchColumn();
      }

      jexit([
        'ok' => true,
        'running' => $running,
        'runtime' => $runtime,
        'active_product' => $activeProduct,
        'active_stats' => $activeStats,
        'history' => $history,
        'quarantine_available' => $qaQty,
        'pending_plc' => $pendingPlc,
        'settings' => $settings,
        'plc_tags' => $plcTags,
        'selected_plc_tag' => $selectedPlcTag,
      ]);
    }

    if ($action === 'select_product') {
      $d = read_json();
      $date = (string)($d['date'] ?? tz_today($APP_TZ));
      $nextProductId = (int)($d['product_id'] ?? 0);
      $qaQty = max(0, (int)($d['qa_qty'] ?? 0));
      $notes = trim((string)($d['notes'] ?? ''));
      if ($nextProductId <= 0) jexit(['ok'=>false,'error'=>'Producto inválido'], 400);

      $running = get_running_shift($pdo,$companyId,$locationId,$uid,$date);
      if (!$running) jexit(['ok'=>false,'error'=>'Debes iniciar turno antes de seleccionar artículo.'], 409);

      ensure_shift_runtime($pdo,$companyId,$locationId,$date,(int)$running['shift'],(int)$running['id'],$uid);
      $runtime = get_shift_runtime($pdo,$companyId,$locationId,$date,(int)$running['shift']);
      if (!$runtime) jexit(['ok'=>false,'error'=>'No se pudo preparar runtime del turno.'], 500);

      $currentId = (int)($runtime['active_product_id'] ?? 0);
      if ($currentId === $nextProductId) {
        jexit(['ok'=>true,'same'=>true]);
      }

      $qaWhId = wh_id_by_code($pdo,$companyId,$locationId,PACK_QA_CODE);
      if ($currentId > 0 && $qaWhId <= 0) {
        jexit(['ok'=>false,'error'=>'No existe almacén QA / Cuarentena configurado.'], 409);
      }

      $pdo->beginTransaction();

      if ($currentId > 0) {
        if ($qaQty <= 0) {
          $pdo->rollBack();
          jexit(['ok'=>false,'error'=>'Debes capturar cantidad a cuarentena antes de cambiar de producto.'], 409);
        }

        $st = $pdo->prepare("
          INSERT INTO pack_changeovers (company_id, location_id, work_date, shift, from_product_id, to_product_id, qa_warehouse_id, qa_qty, changed_at, notes, created_by, created_at)
          VALUES (?, ?, ?, ?, ?, ?, ?, ?, NOW(), ?, ?, NOW())
        ");
        $st->execute([$companyId,$locationId,$date,(int)$running['shift'],$currentId,$nextProductId,$qaWhId,$qaQty,$notes,$uid]);
        $changeoverId = (int)$pdo->lastInsertId();

        $st = $pdo->prepare("
          INSERT INTO pack_quarantine_entries (company_id, location_id, work_date, shift, product_id, warehouse_id, qty, reason, source_changeover_id, entry_time, status, notes, created_by, created_at)
          VALUES (?, ?, ?, ?, ?, ?, ?, 'CHANGEOVER', ?, NOW(), 'AVAILABLE', ?, ?, NOW())
        ");
        $st->execute([$companyId,$locationId,$date,(int)$running['shift'],$currentId,$qaWhId,$qaQty,$changeoverId,$notes,$uid]);
      }

      $st = $pdo->prepare("
        UPDATE pack_shift_runtime
        SET active_product_id=?, active_since=NOW(), updated_by=?, updated_at=NOW()
        WHERE id=? AND company_id=? AND location_id=?
      ");
      $st->execute([$nextProductId,$uid,(int)$runtime['id'],$companyId,$locationId]);

      $pdo->commit();
      jexit(['ok'=>true]);
    }

    if ($action === 'report_pallet') {
      $d = read_json();
      $date = (string)($d['date'] ?? tz_today($APP_TZ));
      $sourceMode = strtoupper(trim((string)($d['source_mode'] ?? 'MANUAL')));
      if (!in_array($sourceMode, ['MANUAL','PLC'], true)) $sourceMode = 'MANUAL';
      $plcSignalId = (int)($d['plc_signal_id'] ?? 0);

      $running = get_running_shift($pdo,$companyId,$locationId,$uid,$date);
      if (!$running) jexit(['ok'=>false,'error'=>'Debes iniciar turno antes de reportar tarimas.'], 409);

      $runtime = get_shift_runtime($pdo,$companyId,$locationId,$date,(int)$running['shift']);
      $activeProductId = (int)($runtime['active_product_id'] ?? 0);
      if ($activeProductId <= 0) jexit(['ok'=>false,'error'=>'Primero selecciona un artículo activo.'], 409);

      $st = $pdo->prepare("
        SELECT p.id AS product_id, p.name, p.sku, s.pieces_per_pallet, s.dlx_code
        FROM pack_product_specs s
        JOIN products p ON p.id = s.product_id
        WHERE s.company_id=? AND s.location_id=? AND s.product_id=? AND s.active=1
        LIMIT 1
      ");
      $st->execute([$companyId,$locationId,$activeProductId]);
      $item = $st->fetch(PDO::FETCH_ASSOC);
      if (!$item) jexit(['ok'=>false,'error'=>'No existe configuración activa para el producto.'], 404);

      $ptId = wh_id_by_code($pdo,$companyId,$locationId,PACK_PT_CODE);
      if ($ptId <= 0) jexit(['ok'=>false,'error'=>'No existe almacén PT configurado.'], 409);

      $selectedPlcTag = null;
      if ($sourceMode === 'PLC') {
        if (empty($settings['plc_enabled'])) jexit(['ok'=>false,'error'=>'La validación PLC no está habilitada.'], 409);
        if ($plcSignalId <= 0) jexit(['ok'=>false,'error'=>'No se indicó TAG PLC a consumir.'], 400);

        $selectedPlcTag = get_selected_plc_tag($pdo, $companyId, $locationId, $plcSignalId);
        if (!$selectedPlcTag) jexit(['ok'=>false,'error'=>'El TAG PLC seleccionado no existe o está deshabilitado.'], 404);
        if ((int)$selectedPlcTag['signal_state'] !== 1) jexit(['ok'=>false,'error'=>'El TAG PLC no está activo en este momento.'], 409);
      }

      $folio = next_pallet_folio($pdo,$companyId,$locationId,$date);
      $serial = next_serial($pdo,$companyId,$locationId,$activeProductId,$date);
      $labelCode = ((string)$item['dlx_code'] !== '' ? $item['dlx_code'] . '-' : '') . $serial;
      $entryTime = combine_local_now_date($date, $APP_TZ);
      $piecesPerPallet = (int)$item['pieces_per_pallet'];

      $pdo->beginTransaction();

      $st = $pdo->prepare("
        INSERT INTO pack_finished_pallets (company_id, location_id, work_date, shift, shift_session_id, pallet_folio, serial_no, label_code, product_id, warehouse_id, entry_time, pallet_qty, pieces_per_pallet, total_pieces, source_mode, plc_signal_id, status, created_by, created_at)
        VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, 'POSTED', ?, NOW())
      ");
      $st->execute([
        $companyId,
        $locationId,
        $date,
        (int)$running['shift'],
        (int)$running['id'],
        $folio,
        $serial,
        $labelCode,
        $activeProductId,
        $ptId,
        $entryTime,
        $piecesPerPallet,
        $piecesPerPallet,
        $sourceMode,
        ($sourceMode === 'PLC' ? (int)$selectedPlcTag['id'] : null),
        $uid
      ]);

      $palletId = (int)$pdo->lastInsertId();

      $st = $pdo->prepare("
        INSERT INTO pack_serial_registry (company_id, location_id, product_id, serial_no, pallet_id, created_at)
        VALUES (?, ?, ?, ?, ?, NOW())
      ");
      $st->execute([$companyId,$locationId,$activeProductId,$serial,$palletId]);

      insert_production_entry(
        $pdo,
        $companyId,
        $locationId,
        $activeProductId,
        $ptId,
        $piecesPerPallet,
        $entryTime,
        $date,
        (int)$running['shift'],
        $uid
      );

      if ($sourceMode === 'PLC') {
        $st = $pdo->prepare("
          UPDATE pack_plc_signals
          SET signal_state=0,
              status='IDLE',
              consumed_at=NOW(),
              updated_at=NOW()
          WHERE id=? AND company_id=? AND location_id=? AND enabled=1 AND signal_state=1
        ");
        $st->execute([(int)$selectedPlcTag['id'],$companyId,$locationId]);

        if ($st->rowCount() === 0) {
          throw new RuntimeException('La señal PLC ya fue consumida o no está activa.');
        }
      }

      $st = $pdo->prepare("
        UPDATE pack_shift_runtime
        SET last_report_at=NOW(), updated_by=?, updated_at=NOW()
        WHERE id=?
      ");
      $st->execute([$uid,(int)$runtime['id']]);

      $labelDir = ensure_label_dir(__DIR__);
      $safeSerial = preg_replace('/[^A-Za-z0-9\-]/', '_', $serial);
      $relPath = 'labels/' . $safeSerial . '.pdf';
      $absPath = __DIR__ . '/' . $relPath;

      make_label_pdf($absPath, [
        'title' => 'HIELO BLANQUITA',
        'company' => 'Empresa: Hielo Bolsa',
        'product_name' => (string)$item['sku'],
        'description' => (string)$item['name'],
        'serial_no' => $serial,
        'pieces_per_pallet' => $piecesPerPallet,
        'entry_time' => $entryTime,
      ]);

      $st = $pdo->prepare("UPDATE pack_finished_pallets SET label_pdf_path=? WHERE id=? AND company_id=? AND location_id=?");
      $st->execute([$relPath,$palletId,$companyId,$locationId]);

      $pdo->commit();

      jexit([
        'ok' => true,
        'pallet_id' => $palletId,
        'folio' => $folio,
        'serial_no' => $serial,
        'label_code' => $labelCode,
        'label_url' => '?action=label_pdf&id=' . $palletId,
        'entry_time' => $entryTime,
        'product_name' => $item['name'],
        'pieces_per_pallet' => $piecesPerPallet,
        'shift' => (int)$running['shift'],
      ]);
    }

    if ($action === 'plc_validate') {
      $d = read_json();
      $date = (string)($d['date'] ?? tz_today($APP_TZ));

      if (empty($settings['plc_enabled'])) jexit(['ok'=>false,'error'=>'PLC no habilitado en configuración.'], 409);
      if (empty($settings['plc_tag_id'])) jexit(['ok'=>false,'error'=>'No hay TAG PLC seleccionado en configuración.'], 409);

      $running = get_running_shift($pdo,$companyId,$locationId,$uid,$date);
      if (!$running) jexit(['ok'=>false,'error'=>'Debes iniciar turno antes de validar PLC.'], 409);

      $runtime = get_shift_runtime($pdo,$companyId,$locationId,$date,(int)$running['shift']);
      $activeProductId = (int)($runtime['active_product_id'] ?? 0);
      if ($activeProductId <= 0) jexit(['ok'=>false,'error'=>'Debes seleccionar artículo activo antes de validar PLC.'], 409);

      $tag = get_selected_plc_tag($pdo, $companyId, $locationId, (int)$settings['plc_tag_id']);
      if (!$tag) jexit(['ok'=>false,'error'=>'El TAG PLC configurado no existe o está deshabilitado.'], 404);

      if ((int)$tag['signal_state'] !== 1) {
        jexit(['ok'=>false,'error'=>'No hay señal PLC activa pendiente para procesar.'], 404);
      }

      jexit([
        'ok'=>true,
        'signal_id'=>(int)$tag['id'],
        'tag_name'=>(string)($tag['tag_name'] ?? ''),
        'plc_name'=>(string)($tag['plc_name'] ?? ''),
      ]);
    }

    if ($action === 'label_pdf') {
      $id = (int)($_GET['id'] ?? 0);
      if ($id <= 0) jexit(['ok'=>false,'error'=>'ID inválido'], 400);

      $st = $pdo->prepare("
        SELECT label_pdf_path
        FROM pack_finished_pallets
        WHERE id=? AND company_id=? AND location_id=?
        LIMIT 1
      ");
      $st->execute([$id,$companyId,$locationId]);
      $r = $st->fetch(PDO::FETCH_ASSOC);

      if (!$r || empty($r['label_pdf_path'])) jexit(['ok'=>false,'error'=>'Etiqueta no encontrada'], 404);

      $abs = __DIR__ . '/' . $r['label_pdf_path'];
      if (!is_file($abs)) jexit(['ok'=>false,'error'=>'Archivo PDF no encontrado'], 404);

      header('Content-Type: application/pdf');
      header('Content-Disposition: inline; filename="' . basename($abs) . '"');
      readfile($abs);
      exit;
    }

    jexit(['ok'=>false,'error'=>'Acción no válida'], 404);
  } catch (Throwable $e) {
    if ($pdo->inTransaction()) $pdo->rollBack();
    jexit([
      'ok' => false,
      'error' => 'SERVER_EXCEPTION',
      'msg' => $e->getMessage(),
      'line' => $e->getLine(),
      'file' => basename($e->getFile()),
      'action' => $action
    ], 500);
  }
}

/* ===================== UI ===================== */
$csrf = csrf_token();
$items = get_pack_items($pdo, $companyId, $locationId);

$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>Hielo Bolsa · Producción</title>
  <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:#081426; --bg2:#0d2038; --panel:#0f1f38; --line:rgba(255,255,255,.08);
      --txt:#eef4ff; --muted:#9fb4d5; --ok:#2dd4bf; --warn:#f59e0b; --bad:#ef4444; --blue:#4c89ff;
    }
    body{background:linear-gradient(180deg,var(--bg2),var(--bg1));color:var(--txt);min-height:100vh}
    .cardx{background:rgba(15,31,56,.95);border:1px solid var(--line);border-radius:24px;box-shadow:0 16px 40px rgba(0,0,0,.28)}
    .btn-main{background:linear-gradient(135deg,#53d7b8,#4c89ff);border:0;color:#071223;font-weight:700}
    .btn-main:hover{filter:brightness(1.04);color:#071223}
    .metric{font-size:2rem;font-weight:800;line-height:1}
    .muted{color:var(--muted)}
    .table{color:var(--txt)}
    .table>:not(caption)>*>*{background:transparent;color:var(--txt);border-color:rgba(255,255,255,.08)}
    .badge-soft{background:rgba(255,255,255,.08);border:1px solid rgba(255,255,255,.1)}
    .timer{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:1.1rem}
    .pulse{animation:pulse 1.4s infinite}
    @keyframes pulse{0%{opacity:1}50%{opacity:.55}100%{opacity:1}}
    .form-control,.form-select{background:#0f2747;border:1px solid rgba(255,255,255,.12);color:#eef4ff}
    .form-control:focus,.form-select:focus{background:#153259;color:#eef4ff;border-color:#53d7b8;box-shadow:0 0 0 .2rem rgba(83,215,184,.12)}
    .form-label{color:#cfe0ff}
    .tag-pill{font-size:.85rem}
  </style>
</head>
<body>
<div class="container-fluid py-3">
  <div class="d-flex justify-content-between align-items-center mb-3 flex-wrap gap-2">
    <div>
      <div class="h3 m-0">Hielo Bolsa · Producción</div>
      <div class="muted"><?= h($companyName) ?> · <?= h($locationName) ?></div>
    </div>
    <div class="d-flex gap-2 flex-wrap">
      <button class="btn btn-outline-light" id="btnSettings"><i class="bi bi-sliders"></i> Configuración</button>
      <button class="btn btn-outline-info" id="btnSelectProduct"><i class="bi bi-box2"></i> Seleccionar artículo</button>
      <button class="btn btn-outline-warning" id="btnPLC" style="display:none;"><i class="bi bi-cpu"></i> Validar PLC</button>
      <button class="btn btn-outline-success" id="btnShiftStart"><i class="bi bi-play-fill"></i> Iniciar turno</button>
      <button class="btn btn-outline-danger" id="btnShiftEnd" disabled><i class="bi bi-stop-fill"></i> Finalizar turno</button>
      <a href="index.php" class="btn btn-outline-light"><i class="bi bi-arrow-left"></i> Menú</a>
    </div>
  </div>

  <div class="row g-3">
    <div class="col-12 col-xl-4">
      <div class="cardx p-3 h-100">
        <label class="form-label">Fecha</label>
        <input type="date" id="datePick" class="form-control mb-3" value="<?= h(tz_today($APP_TZ)) ?>">

        <div class="d-flex justify-content-between align-items-center mb-2">
          <span class="badge badge-soft rounded-pill" id="shiftBadge">Sin turno</span>
          <span class="timer" id="clockNow">--:--:--</span>
        </div>
        <div class="muted small">Tiempo de turno</div>
        <div class="timer mb-3" id="shiftTimer">00:00:00</div>

        <div class="cardx p-3 mb-3">
          <div class="muted small mb-1">TAG PLC configurado</div>
          <div id="plcTagInfo" class="small muted">Sin TAG seleccionado</div>
        </div>

       

        <div class="mt-3 cardx p-3">
          <div class="muted small">Cuarentena disponible</div>
          <div class="metric" id="qaAvailable">0</div>
          <div class="muted small">piezas en almacén QA</div>
        </div>
      </div>
    </div>

    <div class="col-12 col-xl-8">
      <div class="cardx p-3 mb-3">
        <div class="d-flex justify-content-between align-items-center flex-wrap gap-2">
          <div>
            <div class="h5 m-0">Artículo activo</div>
            <div class="muted">Selecciona un artículo y luego reporta tarimas del turno</div>
          </div>
          <button class="btn btn-main" id="btnReportManual"><i class="bi bi-plus-circle"></i> Reportar tarima</button>
        </div>
        <hr style="border-color:rgba(255,255,255,.08)">
        <div id="activeCard">
          <div class="muted">Sin artículo activo.</div>
        </div>
      </div>

      <div class="cardx p-3 mb-3">
  <div class="d-flex justify-content-between align-items-center flex-wrap gap-2 mb-3">
    <div>
      <div class="h5 m-0">Indicadores del turno</div>
      <div class="muted">Resumen operativo del artículo activo y señales PLC</div>
    </div>
  </div>

  <div class="row g-3 text-center">
    <div class="col-12 col-md-6">
      <div class="cardx p-3 h-100">
        <div class="muted small">Objetivo turno</div>
        <div class="metric" id="targetShift">0</div>
        <div class="muted small">tarimas</div>
      </div>
    </div>

    <div class="col-12 col-md-6">
      <div class="cardx p-3 h-100">
        <div class="muted small">PLC pendientes</div>
        <div class="metric" id="pendingPLC">0</div>
        <div class="muted small">señales</div>
      </div>
    </div>

    <div class="col-12 col-md-6">
      <div class="cardx p-3 h-100">
        <div class="muted small">Tarimas artículo</div>
        <div class="metric" id="activePallets">0</div>
        <div class="muted small">en turno</div>
      </div>
    </div>

    <div class="col-12 col-md-6">
      <div class="cardx p-3 h-100">
        <div class="muted small">Piezas artículo</div>
        <div class="metric" id="activePieces">0</div>
        <div class="muted small">acumuladas</div>
      </div>
    </div>
  </div>
</div>

      <div class="cardx p-3">
        <div class="d-flex justify-content-between align-items-center flex-wrap gap-2 mb-2">
          <div>
            <div class="h5 m-0">Histórico del turno</div>
            <div class="muted">Cada tarima genera etiqueta PDF con serie iniciando con T</div>
          </div>
        </div>
        <div class="table-responsive">
          <table class="table table-sm align-middle mb-0">
            <thead>
              <tr>
                <th>Hora</th>
                <th>Turno</th>
                <th>Artículo</th>
                <th>Folio</th>
                <th>Serie</th>
                <th>Origen</th>
                <th>Etiqueta</th>
                <th class="text-end">Pzas/Tarima</th>
              </tr>
            </thead>
            <tbody id="logBody">
              <tr><td colspan="8" class="muted">Sin capturas.</td></tr>
            </tbody>
          </table>
        </div>
      </div>
    </div>
  </div>
</div>

<div class="modal fade" id="mdlShift" tabindex="-1" aria-hidden="true">
  <div class="modal-dialog modal-dialog-centered">
    <div class="modal-content" style="background:#0f1f38;color:#eef4ff">
      <div class="modal-header"><h5 class="modal-title">Iniciar turno</h5><button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button></div>
      <div class="modal-body">
        <label class="form-label">Turno</label>
        <select id="shiftSel" class="form-select">
          <option value="1">Turno 1 (07:00 - 15:00)</option>
          <option value="2">Turno 2 (15:00 - 22:30)</option>
          <option value="3">Turno 3 (22:30 - 07:00)</option>
        </select>
      </div>
      <div class="modal-footer"><button class="btn btn-outline-light" data-bs-dismiss="modal">Cancelar</button><button class="btn btn-main" id="doShiftStart">Iniciar</button></div>
    </div>
  </div>
</div>

<div class="modal fade" id="mdlSettings" tabindex="-1" aria-hidden="true">
  <div class="modal-dialog modal-dialog-centered">
    <div class="modal-content" style="background:#0f1f38;color:#eef4ff">
      <div class="modal-header"><h5 class="modal-title">Configuración</h5><button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button></div>
      <div class="modal-body">
        <div class="mb-3">
          <label class="form-label">Zona horaria</label>
          <input id="cfgTimezone" class="form-control" placeholder="America/Monterrey">
        </div>
        <div class="mb-3">
          <label class="form-label">Objetivo por turno (tarimas)</label>
          <input id="cfgTarget" type="number" min="0" class="form-control">
        </div>
        <div class="mb-3">
          <label class="form-label">TAG PLC a monitorear</label>
          <select id="cfgPlcTag" class="form-select">
            <option value="">Sin selección</option>
          </select>
          <div class="small muted mt-2">El sistema solo procesará la fila seleccionada y regresará su estado a 0 al consumirla.</div>
        </div>
        <div class="form-check form-switch">
          <input class="form-check-input" type="checkbox" id="cfgPlcEnabled">
          <label class="form-check-label" for="cfgPlcEnabled">Habilitar lectura automática por PLC</label>
        </div>
      </div>
      <div class="modal-footer"><button class="btn btn-outline-light" data-bs-dismiss="modal">Cerrar</button><button class="btn btn-main" id="doSaveSettings">Guardar</button></div>
    </div>
  </div>
</div>

<div class="modal fade" id="mdlSelectProduct" tabindex="-1" aria-hidden="true">
  <div class="modal-dialog modal-lg modal-dialog-centered">
    <div class="modal-content" style="background:#0f1f38;color:#eef4ff">
      <div class="modal-header"><h5 class="modal-title">Seleccionar artículo</h5><button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button></div>
      <div class="modal-body">
        <div id="productList" class="row g-3"></div>
      </div>
    </div>
  </div>
</div>

<div class="modal fade" id="mdlChangeover" tabindex="-1" aria-hidden="true">
  <div class="modal-dialog modal-dialog-centered">
    <div class="modal-content" style="background:#0f1f38;color:#eef4ff">
      <div class="modal-header"><h5 class="modal-title">Cambio de producto</h5><button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button></div>
      <div class="modal-body">
        <div class="mb-2">Antes de cambiar, captura la cantidad a cuarentena del artículo actual.</div>
        <div class="small muted mb-3" id="changeoverText"></div>
        <div class="mb-3"><label class="form-label">Cantidad a cuarentena</label><input id="changeQaQty" type="number" min="0" class="form-control"></div>
        <div class="mb-3"><label class="form-label">Comentario</label><textarea id="changeNotes" class="form-control" rows="3"></textarea></div>
      </div>
      <div class="modal-footer"><button class="btn btn-outline-light" data-bs-dismiss="modal">Cancelar</button><button class="btn btn-main" id="doChangeProduct">Confirmar cambio</button></div>
    </div>
  </div>
</div>

<div class="modal fade" id="mdlConfirmReport" tabindex="-1" aria-hidden="true">
  <div class="modal-dialog modal-dialog-centered">
    <div class="modal-content" style="background:#0f1f38;color:#eef4ff">
      <div class="modal-header"><h5 class="modal-title">Confirmar producción</h5><button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button></div>
      <div class="modal-body">
        <div id="confirmText" class="mb-2"></div>
        <div class="small muted">Se generará una etiqueta PDF con serie iniciando con T.</div>
      </div>
      <div class="modal-footer"><button class="btn btn-outline-light" data-bs-dismiss="modal">Cancelar</button><button class="btn btn-main" id="doReportManual">Confirmar</button></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 APP_TZ = <?= json_encode($APP_TZ) ?>;
const $ = id => document.getElementById(id);

async function parseResponse(r){
  const t = await r.text();
  try { return JSON.parse(t); } catch { return {ok:false,error:'NO_JSON',raw:t,http:r.status}; }
}
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'});
  return parseResponse(r);
}
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)
  });
  return parseResponse(r);
}
function showErr(j, fallback='Error'){ alert(j?.msg || j?.error || fallback); console.error(j); }
function esc(s){ return String(s ?? '').replace(/[&<>\"]/g, m => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'}[m])); }

let items = <?= json_encode($items, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>;
let settings = <?= json_encode($settings, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>;
let running = null;
let runtime = null;
let activeProduct = null;
let pendingChangeProductId = 0;
let plcBusy = false;
let plcTags = [];
let selectedPlcTag = null;
let mdlShift, mdlSettings, mdlSelectProduct, mdlChangeover, mdlConfirmReport;

function clockTick(){
  const now = new Date();
  $('clockNow').textContent = now.toLocaleTimeString('es-MX', {hour12:false});
  if (!running?.start_at) { $('shiftTimer').textContent = '00:00:00'; return; }
  const start = new Date(String(running.start_at).replace(' ', 'T'));
  const diff = Math.max(0, Math.floor((Date.now() - start.getTime()) / 1000));
  const hh = String(Math.floor(diff / 3600)).padStart(2,'0');
  const mm = String(Math.floor((diff % 3600) / 60)).padStart(2,'0');
  const ss = String(diff % 60).padStart(2,'0');
  $('shiftTimer').textContent = `${hh}:${mm}:${ss}`;
}

function paintShift(){
  const has = !!running;
  $('btnShiftEnd').disabled = !has;
  $('btnReportManual').disabled = !has || !activeProduct;
  $('btnPLC').disabled = !has || !settings?.plc_enabled || !activeProduct || !settings?.plc_tag_id;
  if (!has) {
    $('shiftBadge').className = 'badge text-bg-secondary rounded-pill';
    $('shiftBadge').textContent = 'Sin turno';
  } else {
    $('shiftBadge').className = 'badge text-bg-success rounded-pill pulse';
    $('shiftBadge').textContent = `Turno ${running.shift} activo`;
  }
}

function paintSettings(){
  const target = Number(settings?.shift_target_pallets || 0);

  $('cfgTimezone').value = settings?.timezone || APP_TZ;
  $('cfgTarget').value = target;
  $('cfgPlcEnabled').checked = !!Number(settings?.plc_enabled || 0);
  $('targetShift').textContent = String(target);

  const sel = $('cfgPlcTag');
  if (sel) {
    sel.innerHTML = '<option value="">Sin selección</option>';
    for (const t of plcTags) {
      const txt = `${t.plc_name || 'PLC'} · ${t.tag_name || ('TAG #' + t.id)} · bit=${t.signal_state}`;
      sel.insertAdjacentHTML('beforeend', `<option value="${t.id}">${esc(txt)}</option>`);
    }
    sel.value = settings?.plc_tag_id ? String(settings.plc_tag_id) : '';
  }

  paintSelectedTagInfo();
}

function paintSelectedTagInfo(){
  if (!selectedPlcTag) {
    $('plcTagInfo').textContent = 'Sin TAG seleccionado';
    return;
  }
  $('plcTagInfo').innerHTML = `
    <span class="badge text-bg-info tag-pill">ID ${esc(selectedPlcTag.id)}</span>
    <span class="ms-1">${esc(selectedPlcTag.plc_name || 'PLC')}</span>
    <span class="ms-1">· ${esc(selectedPlcTag.tag_name || '')}</span>
    <span class="ms-1">· bit ${esc(selectedPlcTag.signal_state)}</span>
    <span class="ms-1">· ${esc(selectedPlcTag.status || '')}</span>
  `;
}

function paintPlcTags(rows){
  const tb = $('plcBody');
  if (!tb) return;

  tb.innerHTML = '';
  if (!rows?.length) {
    tb.innerHTML = '<tr><td colspan="7" class="muted">Sin TAGs configurados.</td></tr>';
    return;
  }
  for (const r of rows) {
    tb.insertAdjacentHTML('beforeend', `
      <tr>
        <td>${esc(r.id)}</td>
        <td>${esc(r.plc_name || '-')}</td>
        <td>${esc(r.ip_address || '-')}</td>
        <td>${esc(r.tag_name || r.signal_name || '-')}</td>
        <td>${esc(r.signal_state)}</td>
        <td>${esc(r.status || '-')}</td>
        <td>${esc(r.last_value_at || r.updated_at || '-')}</td>
      </tr>
    `);
  }
}

function paintActive(stats={pallets:0,pieces:0}){
  if (!activeProduct) {
    $('activeCard').innerHTML = '<div class="muted">Sin artículo activo.</div>';
    $('activePallets').textContent = '0';
    $('activePieces').textContent = '0';
    return;
  }
  $('activePallets').textContent = String(stats?.pallets || 0);
  $('activePieces').textContent = String(stats?.pieces || 0);
  $('activeCard').innerHTML = `
    <div class="row g-3 align-items-center">
      <div class="col-12 col-lg-8">
        <div class="h4 mb-1">${esc(activeProduct.name)}</div>
        <div class="muted">${esc(activeProduct.sku)} · DLX ${esc(activeProduct.dlx_code || '')}</div>
      </div>
      <div class="col-12 col-lg-4 text-lg-end">
        <div class="muted small">Piezas por tarima</div>
        <div class="metric">${esc(activeProduct.pieces_per_pallet)}</div>
      </div>
    </div>`;
}

function paintHistory(rows){
  const tb = $('logBody');
  tb.innerHTML = '';
  if (!rows?.length) {
    tb.innerHTML = '<tr><td colspan="8" class="muted">Sin capturas.</td></tr>';
    return;
  }
  for (const r of rows) {
    const link = r.label_pdf_path ? `<a href="?action=label_pdf&id=${r.id}" target="_blank" class="btn btn-sm btn-outline-light">PDF</a>` : '-';
    tb.insertAdjacentHTML('beforeend', `
      <tr>
        <td>${esc(String(r.entry_time || '').replace('T',' '))}</td>
        <td>${esc(r.shift)}</td>
        <td>${esc(r.product_name)}</td>
        <td>${esc(r.pallet_folio)}</td>
        <td>${esc(r.serial_no)}</td>
        <td>${esc(r.source_mode || '-')}</td>
        <td>${link}</td>
        <td class="text-end">${esc(r.pieces_per_pallet)}</td>
      </tr>`);
  }
}

function renderProductList(){
  const wrap = $('productList');
  wrap.innerHTML = '';
  if (!items.length) {
    wrap.innerHTML = '<div class="col-12 muted">No hay artículos configurados.</div>';
    return;
  }
  for (const it of items) {
    wrap.insertAdjacentHTML('beforeend', `
      <div class="col-12 col-md-6 col-xl-4">
        <div class="cardx p-3 h-100">
          <div class="fw-bold">${esc(it.name)}</div>
          <div class="small muted">${esc(it.sku)} · DLX ${esc(it.dlx_code)}</div>
          <div class="small mt-2">Pz por tarima: <b>${esc(it.pieces_per_pallet)}</b></div>
          <div class="small muted">Cama: ${esc(it.pieces_per_layer)} · Camas: ${esc(it.layers_per_pallet)}</div>
          <button class="btn btn-main w-100 mt-3" data-product="${it.product_id}">Seleccionar</button>
        </div>
      </div>`);
  }
}

async function loadDay(){
  const j = await apiGet('day_load', {date:$('datePick').value});
  if (!j.ok) { showErr(j, 'Error día'); return; }

  running = j.running || null;
  runtime = j.runtime || null;
  activeProduct = j.active_product || null;
  settings = j.settings || settings;
  plcTags = j.plc_tags || [];
  selectedPlcTag = j.selected_plc_tag || null;

  paintShift();
  paintSettings();
  paintPlcTags(plcTags);
  paintActive(j.active_stats || {pallets:0,pieces:0});
  paintHistory(j.history || []);
  $('qaAvailable').textContent = String(j.quarantine_available || 0);
  $('pendingPLC').textContent = String(j.pending_plc || 0);
}

async function startShift(){
  const j = await apiPost('shift_start', {date:$('datePick').value, shift:parseInt($('shiftSel').value,10)});
  if (!j.ok) { showErr(j, 'Error al iniciar turno'); return; }
  mdlShift.hide();
  await loadDay();
}
async function endShift(){
  if (!confirm('¿Finalizar turno?')) return;
  const j = await apiPost('shift_end', {date:$('datePick').value});
  if (!j.ok) { showErr(j, 'Error al finalizar turno'); return; }
  await loadDay();
}
async function saveSettings(){
  const j = await apiPost('save_settings', {
    timezone: $('cfgTimezone').value,
    shift_target_pallets: parseInt($('cfgTarget').value || '0', 10),
    plc_enabled: $('cfgPlcEnabled').checked ? 1 : 0,
    plc_tag_id: parseInt($('cfgPlcTag').value || '0', 10)
  });
  if (!j.ok) { showErr(j, 'Error al guardar configuración'); return; }

  mdlSettings.hide();
  settings.timezone = $('cfgTimezone').value;
  settings.shift_target_pallets = parseInt($('cfgTarget').value || '0', 10);
  settings.plc_enabled = $('cfgPlcEnabled').checked ? 1 : 0;
  settings.plc_tag_id = parseInt($('cfgPlcTag').value || '0', 10) || null;

  await loadDay();
}

function beginSelectProduct(productId){
  if (!running) { alert('Debes iniciar turno primero.'); return; }
  if (activeProduct && Number(activeProduct.product_id) !== Number(productId)) {
    pendingChangeProductId = Number(productId);
    $('changeQaQty').value = '';
    $('changeNotes').value = '';
    $('changeoverText').textContent = `Actual: ${activeProduct.name} → Nuevo: ${(items.find(x => Number(x.product_id) === Number(productId)) || {}).name || ''}`;
    mdlChangeover.show();
    return;
  }
  confirmSelectProduct(productId, 0, '');
}

async function confirmSelectProduct(productId, qaQty = 0, notes = ''){
  const j = await apiPost('select_product', {
    date: $('datePick').value,
    product_id: Number(productId),
    qa_qty: Number(qaQty || 0),
    notes
  });
  if (!j.ok) { showErr(j, 'Error al seleccionar artículo'); return; }
  mdlSelectProduct.hide();
  mdlChangeover.hide();
  pendingChangeProductId = 0;
  await loadDay();
}

function openConfirmReport(){
  if (!running) { alert('Debes iniciar turno primero.'); return; }
  if (!activeProduct) { alert('Selecciona un artículo primero.'); return; }
  $('confirmText').innerHTML = `Se reportará 1 tarima de <b>${esc(activeProduct.name)}</b> con <b>${esc(activeProduct.pieces_per_pallet)}</b> piezas.`;
  mdlConfirmReport.show();
}

async function reportManual(){
  const j = await apiPost('report_pallet', {date:$('datePick').value, source_mode:'MANUAL'});
  if (!j.ok) { showErr(j, 'Error al reportar tarima'); return; }
  mdlConfirmReport.hide();
  alert(`Tarima registrada\nArtículo: ${j.product_name}\nFolio: ${j.folio}\nSerie: ${j.serial_no}`);
  if (j.label_url) window.open(j.label_url, '_blank');
  await loadDay();
}

async function validatePLC(){
  const sig = await apiPost('plc_validate', {date:$('datePick').value});
  if (!sig.ok) { showErr(sig, 'Error al validar PLC'); return; }

  const j = await apiPost('report_pallet', {
    date:$('datePick').value,
    source_mode:'PLC',
    plc_signal_id:sig.signal_id
  });
  if (!j.ok) { showErr(j, 'Error al reportar desde PLC'); return; }

  alert(`Reporte PLC registrado\nArtículo: ${j.product_name}\nSerie: ${j.serial_no}`);
  if (j.label_url) window.open(j.label_url, '_blank');
  await loadDay();
}

async function autoProcessPLC(){
  if (plcBusy) return;
  if (!running) return;
  if (!activeProduct) return;
  if (!settings?.plc_enabled) return;
  if (!settings?.plc_tag_id) return;

  plcBusy = true;
  try {
    const sig = await apiPost('plc_validate', {date:$('datePick').value});
    if (!sig.ok) return;

    const j = await apiPost('report_pallet', {
      date:$('datePick').value,
      source_mode:'PLC',
      plc_signal_id:sig.signal_id
    });
    if (!j.ok) {
      console.error('Error al reportar PLC automáticamente', j);
      return;
    }

    console.log('Tarima PLC registrada automáticamente', j);

    if (j.label_url) {
      const w = window.open(j.label_url, '_blank');
      if (w) {
        w.onload = () => {
          try { w.print(); } catch(e) { console.error('No se pudo imprimir automáticamente', e); }
        };
      }
    }

    await loadDay();
  } catch (e) {
    console.error('Fallo en autoProcessPLC', e);
  } finally {
    plcBusy = false;
  }
}

document.addEventListener('DOMContentLoaded', async ()=>{
  mdlShift = bootstrap.Modal.getOrCreateInstance(document.getElementById('mdlShift'));
  mdlSettings = bootstrap.Modal.getOrCreateInstance(document.getElementById('mdlSettings'));
  mdlSelectProduct = bootstrap.Modal.getOrCreateInstance(document.getElementById('mdlSelectProduct'));
  mdlChangeover = bootstrap.Modal.getOrCreateInstance(document.getElementById('mdlChangeover'));
  mdlConfirmReport = bootstrap.Modal.getOrCreateInstance(document.getElementById('mdlConfirmReport'));

  renderProductList();
  await loadDay();
  clockTick();

  setInterval(clockTick, 1000);
  setInterval(autoProcessPLC, 3000);

  $('btnShiftStart').addEventListener('click', ()=> mdlShift.show());
  $('doShiftStart').addEventListener('click', startShift);
  $('btnShiftEnd').addEventListener('click', endShift);
  $('btnSettings').addEventListener('click', ()=> mdlSettings.show());
  $('doSaveSettings').addEventListener('click', saveSettings);
  $('btnSelectProduct').addEventListener('click', ()=> mdlSelectProduct.show());
  $('btnReportManual').addEventListener('click', openConfirmReport);
  $('doReportManual').addEventListener('click', reportManual);
  $('btnPLC').addEventListener('click', validatePLC);
  $('datePick').addEventListener('change', loadDay);

  $('productList').addEventListener('click', (e)=>{
    const b = e.target.closest('[data-product]');
    if (!b) return;
    beginSelectProduct(b.dataset.product);
  });

  $('doChangeProduct').addEventListener('click', ()=>{
    confirmSelectProduct(pendingChangeProductId, $('changeQaQty').value, $('changeNotes').value);
  });
});
</script>
</body>
</html>