<?php
/**
 * revision_rutas_botella.php
 * ============================================================
 * BReport · Planta Botella · REVISIÓN DE RUTAS DE REPARTO
 * ------------------------------------------------------------
 * - Almacén puede revisar existencia actual por ruta móvil RUTA-xx.
 * - Cada ruta se trata como mini almacén.
 * - Inventario oficial: inventory_moves.
 * - IN/ADJ suman, OUT resta.
 * - Movimientos del día se calculan por ventana operativa 07:00 a 06:59.
 * - Permite asignar usuario con rol reparto/repartidor a una ruta.
 * ============================================================
 */

declare(strict_types=1);

const BREPORT_TIMEZONE = 'America/Matamoros';
date_default_timezone_set(BREPORT_TIMEZONE);

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();
$scope = scope();
$companyId = (int)($scope['company_id'] ?? 0);
$locationId = (int)($scope['location_id'] ?? 0);
$action = (string)($_GET['action'] ?? '');

revision_configure_db_timezone($pdo);

$scopeCompanyName = '';
$scopeLocationName = '';
try {
    $st = $pdo->prepare('SELECT name FROM companies WHERE id=? LIMIT 1');
    $st->execute([$companyId]);
    $scopeCompanyName = (string)($st->fetchColumn() ?: ('Empresa #' . $companyId));

    $st = $pdo->prepare('SELECT name FROM locations WHERE id=? AND company_id=? LIMIT 1');
    $st->execute([$locationId, $companyId]);
    $scopeLocationName = (string)($st->fetchColumn() ?: ('Localidad #' . $locationId));
} catch (Throwable $e) {
    $scopeCompanyName = 'Empresa #' . $companyId;
    $scopeLocationName = 'Localidad #' . $locationId;
}

$scopePlantText = mb_strtoupper(trim($scopeCompanyName . ' ' . $scopeLocationName), 'UTF-8');
if (!str_contains($scopePlantText, 'BOTELLA')) {
    http_response_code(403);
    exit('Esta sección corresponde únicamente a Planta Botella.');
}

$canUse = has_role('admin') || has_role('warehouse') || has_role('almacen') || has_role('plant_admin') || has_role('supervisor');
if (!$canUse) {
    http_response_code(403);
    exit('Sin permiso para revisar rutas de reparto.');
}

ensure_route_assignment_schema_revision($pdo);

function revision_timezone_offset(): string {
    $dt = new DateTimeImmutable('now', new DateTimeZone(BREPORT_TIMEZONE));
    $offset = $dt->getOffset();
    $sign = $offset >= 0 ? '+' : '-';
    $offset = abs($offset);
    return sprintf('%s%02d:%02d', $sign, intdiv($offset, 3600), intdiv($offset % 3600, 60));
}

function revision_configure_db_timezone(PDO $pdo): void {
    try { $pdo->exec('SET time_zone = ' . $pdo->quote(revision_timezone_offset())); } catch (Throwable $e) {}
}

function revision_now(): string {
    return (new DateTimeImmutable('now', new DateTimeZone(BREPORT_TIMEZONE)))->format('Y-m-d H:i:s');
}

function revision_operational_today(): string {
    $now = new DateTimeImmutable('now', new DateTimeZone(BREPORT_TIMEZONE));
    if ((int)$now->format('H') < 7) $now = $now->modify('-1 day');
    return $now->format('Y-m-d');
}

function revision_resolve_work_date(string $submittedDate): string {
    $submittedDate = trim($submittedDate);
    if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $submittedDate)) return revision_operational_today();
    return $submittedDate;
}

function revision_operational_window(string $workDate): array {
    $tz = new DateTimeZone(BREPORT_TIMEZONE);
    $start = new DateTimeImmutable($workDate . ' 07:00:00', $tz);
    $end = $start->modify('+1 day');
    return ['start'=>$start->format('Y-m-d H:i:s'), 'end'=>$end->format('Y-m-d H:i:s')];
}

function jexit_revision(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_revision(): array {
    $raw = file_get_contents('php://input');
    $arr = json_decode($raw ?: '[]', true);
    return is_array($arr) ? $arr : [];
}

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

function table_exists_revision(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 ensure_route_assignment_schema_revision(PDO $pdo): void {
    if (!table_exists_revision($pdo, 'botella_route_user_assignments')) {
        $pdo->exec("CREATE TABLE botella_route_user_assignments (
            id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
            company_id BIGINT UNSIGNED NOT NULL,
            location_id BIGINT UNSIGNED NOT NULL,
            user_id BIGINT UNSIGNED NOT NULL,
            route_no INT NOT NULL,
            warehouse_id BIGINT UNSIGNED DEFAULT NULL,
            active TINYINT(1) NOT NULL DEFAULT 1,
            assigned_by BIGINT UNSIGNED DEFAULT NULL,
            assigned_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
            notes VARCHAR(255) DEFAULT NULL,
            PRIMARY KEY(id),
            KEY idx_bru_active_user (company_id, location_id, user_id, active),
            KEY idx_bru_route (company_id, location_id, route_no),
            KEY idx_bru_user (user_id)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci");
    }
}

function route_wh_code_revision(int $routeNo): string {
    return 'RUTA-' . str_pad((string)$routeNo, 2, '0', STR_PAD_LEFT);
}

function is_route_wh_code_revision(string $code): bool {
    return (bool)preg_match('/^RUTA[-_ ]?\d+$/i', trim($code));
}

function is_route_visible_for_reparto_revision(array $route): bool {
    $routeNo = (int)($route['route_no'] ?? 0);
    $code = mb_strtoupper(trim((string)($route['route_code'] ?? '')), 'UTF-8');
    $name = mb_strtoupper(trim((string)($route['route_name'] ?? '')), 'UTF-8');
    if ($routeNo === 1) return false;
    if (str_contains($code, 'RUTA-VENTAS') || str_contains($name, 'RUTA DE CARGA DE CLIENTES')) return false;
    return true;
}

function route_warehouse_id_revision(PDO $pdo, int $companyId, int $locationId, int $routeNo, bool $create = false): int {
    $code = route_wh_code_revision($routeNo);
    $st = $pdo->prepare('SELECT id FROM warehouses WHERE company_id=? AND location_id=? AND UPPER(code)=UPPER(?) LIMIT 1');
    $st->execute([$companyId, $locationId, $code]);
    $id = (int)($st->fetchColumn() ?: 0);
    if ($id > 0 || !$create) return $id;

    $st = $pdo->prepare('SELECT route_code, route_name FROM route_access_codes WHERE company_id=? AND location_id=? AND route_no=? AND active=1 LIMIT 1');
    $st->execute([$companyId, $locationId, $routeNo]);
    $r = $st->fetch(PDO::FETCH_ASSOC) ?: [];
    $name = 'Ruta ' . $routeNo;
    if (!empty($r['route_code'])) $name .= ' · ' . (string)$r['route_code'];
    if (!empty($r['route_name'])) $name .= ' · ' . (string)$r['route_name'];

    $st = $pdo->prepare('INSERT INTO warehouses(company_id, location_id, code, name, active, created_at) VALUES(?, ?, ?, ?, 1, NOW())');
    $st->execute([$companyId, $locationId, $code, mb_substr($name, 0, 120, 'UTF-8')]);
    return (int)$pdo->lastInsertId();
}

function route_assignment_revision(PDO $pdo, int $companyId, int $locationId, int $routeNo): ?array {
    $st = $pdo->prepare("SELECT a.id, a.user_id, a.assigned_at, a.notes, u.username, u.full_name, u.phone
        FROM botella_route_user_assignments a
        JOIN users u ON u.id=a.user_id
        WHERE a.company_id=? AND a.location_id=? AND a.route_no=? AND a.active=1
        ORDER BY a.id DESC LIMIT 1");
    $st->execute([$companyId, $locationId, $routeNo]);
    $row = $st->fetch(PDO::FETCH_ASSOC);
    return $row ?: null;
}

function route_stock_totals_revision(PDO $pdo, int $companyId, int $locationId, int $warehouseId, string $workDate): array {
    if ($warehouseId <= 0) return ['current_pieces'=>0.0,'day_in'=>0.0,'day_out'=>0.0,'products'=>0,'idle_products'=>0,'products_rows'=>[]];
    $win = revision_operational_window($workDate);
    $st = $pdo->prepare("SELECT p.id AS product_id, p.sku, p.name,
            COALESCE(ppo.pieces_per_package, p.pieces_per_package, 0) AS pieces_per_package,
            COALESCE(ppo.packages_per_pallet, p.packages_per_pallet, 0) AS packages_per_pallet,
            SUM(CASE WHEN im.move_type IN ('IN','ADJ') THEN im.qty WHEN im.move_type='OUT' THEN -im.qty ELSE 0 END) AS current_qty,
            SUM(CASE WHEN im.move_type IN ('IN','ADJ') AND im.created_at >= ? AND im.created_at < ? THEN im.qty ELSE 0 END) AS day_in,
            SUM(CASE WHEN im.move_type='OUT' AND im.created_at >= ? AND im.created_at < ? THEN im.qty ELSE 0 END) AS day_out
        FROM inventory_moves im
        JOIN products p ON p.id=im.product_id AND p.company_id=im.company_id
        LEFT JOIN product_packaging_options ppo ON ppo.company_id=p.company_id AND ppo.product_id=p.id AND ppo.active=1 AND ppo.is_default=1
        WHERE im.company_id=? AND im.location_id=? AND im.warehouse_id=?
        GROUP BY p.id, p.sku, p.name, ppo.pieces_per_package, ppo.packages_per_pallet, p.pieces_per_package, p.packages_per_pallet
        HAVING current_qty <> 0 OR day_in <> 0 OR day_out <> 0
        ORDER BY p.sku, p.name");
    $st->execute([$win['start'],$win['end'],$win['start'],$win['end'],$companyId,$locationId,$warehouseId]);
    $rows = [];
    $totalCurrent = $totalIn = $totalOut = 0.0;
    $idle = 0;
    foreach ($st->fetchAll(PDO::FETCH_ASSOC) as $r) {
        $current = (float)$r['current_qty'];
        $dayIn = (float)$r['day_in'];
        $dayOut = (float)$r['day_out'];
        $piecesPerPackage = (int)($r['pieces_per_package'] ?? 0);
        $packagesPerPallet = (int)($r['packages_per_pallet'] ?? 0);
        $packages = $piecesPerPackage > 0 ? floor($current / $piecesPerPackage) : 0;
        $pallets = ($piecesPerPackage > 0 && $packagesPerPallet > 0) ? floor($current / ($piecesPerPackage * $packagesPerPallet)) : 0;
        if ($current > 0.0001 && $dayOut <= 0.0001) $idle++;
        $totalCurrent += $current;
        $totalIn += $dayIn;
        $totalOut += $dayOut;
        $rows[] = [
            'product_id'=>(int)$r['product_id'],
            'sku'=>(string)$r['sku'],
            'name'=>(string)$r['name'],
            'current_qty'=>round($current,3),
            'day_in'=>round($dayIn,3),
            'day_out'=>round($dayOut,3),
            'packages'=>(int)$packages,
            'pallets'=>(int)$pallets,
            'no_movement'=>($current > 0.0001 && $dayOut <= 0.0001),
        ];
    }
    return [
        'current_pieces'=>round($totalCurrent,3),
        'day_in'=>round($totalIn,3),
        'day_out'=>round($totalOut,3),
        'products'=>count($rows),
        'idle_products'=>$idle,
        'products_rows'=>$rows,
    ];
}

function routes_status_revision(PDO $pdo, int $companyId, int $locationId, string $workDate): array {
    $st = $pdo->prepare("SELECT id, route_no, route_code, route_name, driver_name, vehicle_unit, sort_order
        FROM route_access_codes
        WHERE company_id=? AND location_id=? AND active=1
        ORDER BY sort_order, route_no");
    $st->execute([$companyId, $locationId]);
    $rows = [];
    foreach ($st->fetchAll(PDO::FETCH_ASSOC) as $route) {
        if (!is_route_visible_for_reparto_revision($route)) continue;
        $routeNo = (int)$route['route_no'];
        $wid = route_warehouse_id_revision($pdo, $companyId, $locationId, $routeNo, false);
        $tot = route_stock_totals_revision($pdo, $companyId, $locationId, $wid, $workDate);
        $assignment = route_assignment_revision($pdo, $companyId, $locationId, $routeNo);
        $rows[] = array_merge($route, [
            'route_no'=>$routeNo,
            'warehouse_id'=>$wid,
            'warehouse_code'=>route_wh_code_revision($routeNo),
            'assignment'=>$assignment,
        ], $tot);
    }
    return $rows;
}

function reparto_users_revision(PDO $pdo): array {
    $st = $pdo->prepare("SELECT DISTINCT u.id, u.username, u.full_name, u.phone
        FROM users u
        JOIN user_roles ur ON ur.user_id=u.id
        JOIN roles r ON r.id=ur.role_id
        WHERE u.active=1 AND LOWER(r.code) IN ('reparto','repartidor')
        ORDER BY u.full_name, u.username");
    $st->execute();
    return $st->fetchAll(PDO::FETCH_ASSOC);
}

if ($action !== '') {
    try {
        need_csrf_revision();
        $workDate = revision_resolve_work_date((string)($_GET['work_date'] ?? revision_operational_today()));

        if ($action === 'bootstrap') {
            jexit_revision([
                'ok'=>true,
                'csrf'=>csrf_token(),
                'work_date'=>$workDate,
                'now'=>revision_now(),
                'user'=>['id'=>(int)$u['id'], 'name'=>(string)($u['full_name'] ?? $u['username'] ?? '')],
                'scope'=>['company'=>$GLOBALS['scopeCompanyName'], 'location'=>$GLOBALS['scopeLocationName']],
                'users'=>reparto_users_revision($pdo),
                'routes'=>routes_status_revision($pdo, $companyId, $locationId, $workDate),
            ]);
        }

        if ($action === 'routes_status') {
            jexit_revision(['ok'=>true, 'work_date'=>$workDate, 'routes'=>routes_status_revision($pdo, $companyId, $locationId, $workDate), 'now'=>revision_now()]);
        }

        if ($action === 'route_detail') {
            $routeNo = (int)($_GET['route_no'] ?? 0);
            if ($routeNo <= 0 || $routeNo === 1) throw new RuntimeException('Ruta inválida para revisión.');
            $route = null;
            foreach (routes_status_revision($pdo, $companyId, $locationId, $workDate) as $r) {
                if ((int)$r['route_no'] === $routeNo) { $route = $r; break; }
            }
            if (!$route) throw new RuntimeException('Ruta no encontrada o no activa.');
            $wid = (int)$route['warehouse_id'];
            $win = revision_operational_window($workDate);
            $moves = [];
            if ($wid > 0) {
                $st = $pdo->prepare("SELECT im.id, im.work_date, im.move_type, im.qty, im.ref_type, im.ref_id, im.notes, im.created_at,
                        p.sku, p.name, u.full_name AS user_name, u.username
                    FROM inventory_moves im
                    JOIN products p ON p.id=im.product_id AND p.company_id=im.company_id
                    LEFT JOIN users u ON u.id=im.created_by
                    WHERE im.company_id=? AND im.location_id=? AND im.warehouse_id=?
                      AND im.created_at >= ? AND im.created_at < ?
                    ORDER BY im.created_at DESC, im.id DESC
                    LIMIT 300");
                $st->execute([$companyId,$locationId,$wid,$win['start'],$win['end']]);
                $moves = $st->fetchAll(PDO::FETCH_ASSOC);
            }
            jexit_revision(['ok'=>true, 'route'=>$route, 'moves'=>$moves, 'work_date'=>$workDate, 'window'=>$win]);
        }

        if ($action === 'assign_user') {
            $d = read_json_revision();
            $routeNo = (int)($d['route_no'] ?? 0);
            $userId = (int)($d['user_id'] ?? 0);
            $notes = trim((string)($d['notes'] ?? ''));
            if ($routeNo <= 1) throw new RuntimeException('Selecciona una ruta de reparto válida.');
            if ($userId <= 0) throw new RuntimeException('Selecciona un usuario repartidor.');

            $validUser = false;
            foreach (reparto_users_revision($pdo) as $usr) {
                if ((int)$usr['id'] === $userId) { $validUser = true; break; }
            }
            if (!$validUser) throw new RuntimeException('El usuario seleccionado no tiene rol reparto/repartidor activo.');

            $st = $pdo->prepare('SELECT route_no, route_code, route_name FROM route_access_codes WHERE company_id=? AND location_id=? AND route_no=? AND active=1 LIMIT 1');
            $st->execute([$companyId,$locationId,$routeNo]);
            $route = $st->fetch(PDO::FETCH_ASSOC);
            if (!$route || !is_route_visible_for_reparto_revision($route)) throw new RuntimeException('Ruta no válida para reparto.');

            $warehouseId = route_warehouse_id_revision($pdo, $companyId, $locationId, $routeNo, true);
            $pdo->beginTransaction();
            $st = $pdo->prepare('UPDATE botella_route_user_assignments SET active=0 WHERE company_id=? AND location_id=? AND route_no=? AND active=1');
            $st->execute([$companyId,$locationId,$routeNo]);
            $st = $pdo->prepare('UPDATE botella_route_user_assignments SET active=0 WHERE company_id=? AND location_id=? AND user_id=? AND active=1');
            $st->execute([$companyId,$locationId,$userId]);
            $st = $pdo->prepare('INSERT INTO botella_route_user_assignments(company_id, location_id, user_id, route_no, warehouse_id, active, assigned_by, assigned_at, notes) VALUES(?, ?, ?, ?, ?, 1, ?, NOW(), ?)');
            $st->execute([$companyId,$locationId,$userId,$routeNo,$warehouseId,(int)$u['id'],$notes !== '' ? $notes : null]);
            $pdo->commit();
            jexit_revision(['ok'=>true, 'message'=>'Repartidor asignado a ' . route_wh_code_revision($routeNo) . '.']);
        }

        jexit_revision(['ok'=>false,'error'=>'Acción no reconocida.'], 404);
    } catch (Throwable $e) {
        if ($pdo->inTransaction()) $pdo->rollBack();
        jexit_revision(['ok'=>false,'error'=>$e->getMessage()], 400);
    }
}

$csrf = csrf_token();
$defaultWorkDate = revision_operational_today();
?>
<!doctype html>
<html lang="es">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>BReport · Revisión de rutas</title>
<style>
:root{--brand:#0b2f55;--brand2:#061b2b;--accent:#1d75d8;--bg:#eef3f9;--card:#fff;--ink:#0f172a;--muted:#64748b;--line:#dbe3ee;--ok:#12a150;--warn:#f59e0b;--danger:#dc3545;--shadow:0 18px 40px rgba(15,59,99,.13);--radius:22px;}
*{box-sizing:border-box}body{margin:0;font-family:system-ui,-apple-system,"Segoe UI",Roboto,Arial,sans-serif;background:radial-gradient(circle at top left,#e7f4ff 0,#eef3f9 42%,#f8fafc 100%);color:var(--ink);font-size:15px}.topbar{background:linear-gradient(135deg,var(--brand2),var(--brand));color:#fff;padding:14px 18px;box-shadow:0 10px 30px rgba(6,27,43,.18)}.topbar-inner{max-width:1380px;margin:0 auto;display:flex;align-items:center;justify-content:space-between;gap:14px;flex-wrap:wrap}.title{font-size:1.35rem;font-weight:950;letter-spacing:-.04em}.sub{color:rgba(255,255,255,.78);font-size:.85rem}.toolbar{display:flex;gap:8px;flex-wrap:wrap;align-items:center}.input,.select{height:42px;border:1px solid #cbd5e1;border-radius:12px;padding:0 12px;background:#fff;color:#0f172a}.btn{border:1px solid transparent;border-radius:12px;height:42px;padding:0 14px;font-weight:850;cursor:pointer;background:#f8fafc;color:#0f172a;display:inline-flex;align-items:center;gap:6px;text-decoration:none}.btn:hover{filter:brightness(.98);transform:translateY(-1px)}.btn-primary{background:var(--accent);color:#fff}.btn-dark{background:var(--brand);color:#fff}.btn-outline{background:#fff;border-color:#bcd3ef;color:#0b3b68}.page{max-width:1380px;margin:18px auto 42px;padding:0 14px}.kpis{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:12px;margin-bottom:14px}.kpi{background:rgba(255,255,255,.96);border:1px solid var(--line);border-radius:20px;padding:14px 16px;box-shadow:var(--shadow)}.kpi span{display:block;color:var(--muted);font-size:.78rem;font-weight:800;text-transform:uppercase;letter-spacing:.03em}.kpi strong{display:block;font-size:1.65rem;color:#09203f;font-weight:950;line-height:1.05;margin-top:5px}.panel{background:rgba(255,255,255,.97);border:1px solid var(--line);border-radius:var(--radius);box-shadow:var(--shadow);padding:16px}.panel-head{display:flex;justify-content:space-between;align-items:flex-start;gap:12px;flex-wrap:wrap;margin-bottom:12px}.panel-title{font-size:1.1rem;font-weight:950}.muted{color:var(--muted)}.routes-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:12px}.route-card{border:1px solid #dbeafe;border-radius:20px;background:#fff;padding:14px;box-shadow:0 10px 24px rgba(15,23,42,.06);cursor:pointer;transition:transform .12s ease, box-shadow .12s ease}.route-card:hover{transform:translateY(-2px);box-shadow:0 16px 32px rgba(15,23,42,.10)}.route-top{display:flex;justify-content:space-between;gap:10px;align-items:flex-start}.route-code{font-size:1.25rem;font-weight:950;color:#08264a}.badge{border-radius:999px;padding:4px 9px;font-size:.72rem;font-weight:850;background:#eaf3ff;color:#174ea6}.badge-ok{background:#dcfce7;color:#166534}.badge-warn{background:#fff7ed;color:#c2410c}.route-metrics{display:grid;grid-template-columns:repeat(3,1fr);gap:8px;margin-top:12px}.metric{background:#f8fbff;border:1px solid #dbeafe;border-radius:14px;padding:9px}.metric small{display:block;color:var(--muted);font-size:.68rem;font-weight:850;text-transform:uppercase}.metric b{display:block;font-size:1rem;margin-top:3px;color:#09203f}.assignment{margin-top:12px;border-top:1px dashed #dbe3ee;padding-top:10px;display:flex;align-items:center;justify-content:space-between;gap:8px}.assignment-name{font-weight:900}.empty{border:1px dashed #cbd5e1;background:#f8fafc;border-radius:18px;padding:20px;text-align:center;color:var(--muted)}.modal{position:fixed;inset:0;background:rgba(2,6,23,.58);display:none;z-index:1000;align-items:center;justify-content:center;padding:18px}.modal.show{display:flex}.modal-card{background:#fff;border-radius:24px;box-shadow:0 25px 80px rgba(0,0,0,.28);width:min(1050px,100%);max-height:92vh;display:flex;flex-direction:column;overflow:hidden}.modal-head,.modal-foot{padding:14px 16px;border-bottom:1px solid var(--line);display:flex;align-items:center;justify-content:space-between;gap:12px}.modal-foot{border-top:1px solid var(--line);border-bottom:0;justify-content:flex-end}.modal-body{padding:16px;overflow:auto}.x{border:0;background:#f1f5f9;border-radius:999px;width:38px;height:38px;font-size:1.4rem;cursor:pointer}.table{width:100%;border-collapse:collapse}.table th,.table td{border-bottom:1px solid var(--line);padding:10px 8px;text-align:left}.table th{background:#f8fafc;color:#64748b;text-transform:uppercase;font-size:.72rem;letter-spacing:.04em}.text-end{text-align:right!important}.text-ok{color:var(--ok);font-weight:900}.text-danger{color:var(--danger);font-weight:900}.split{display:grid;grid-template-columns:1fr 320px;gap:14px}.assign-box{border:1px solid #dbeafe;background:#f8fbff;border-radius:18px;padding:14px;position:sticky;top:0}.notice{background:#fff7ed;border:1px solid #fed7aa;color:#9a3412;border-radius:14px;padding:10px 12px;font-weight:750}.loading{opacity:.7;pointer-events:none}@media(max-width:1100px){.routes-grid{grid-template-columns:repeat(2,minmax(0,1fr))}.kpis{grid-template-columns:repeat(2,minmax(0,1fr))}.split{grid-template-columns:1fr}}@media(max-width:640px){.routes-grid,.kpis{grid-template-columns:1fr}.topbar-inner{align-items:stretch}.toolbar{display:grid;grid-template-columns:1fr; width:100%}.input,.select,.btn{width:100%;justify-content:center}.modal{padding:0;align-items:flex-end}.modal-card{border-radius:24px 24px 0 0;max-height:95vh}.route-metrics{grid-template-columns:1fr}.table thead{display:none}.table,.table tbody,.table tr,.table td{display:block;width:100%}.table tr{border:1px solid var(--line);border-radius:14px;margin-bottom:10px;padding:8px}.table td{border:0;padding:6px 4px}.table td:before{content:attr(data-label);display:block;color:#64748b;font-size:.7rem;text-transform:uppercase;font-weight:850}.text-end{text-align:left!important}}

/* Grid operativo de rutas */
.grid-tools{display:flex;justify-content:space-between;align-items:center;gap:12px;margin:8px 0 12px;flex-wrap:wrap}.search-wrap{position:relative;min-width:min(440px,100%);flex:1}.search-icon{position:absolute;left:12px;top:50%;transform:translateY(-50%);font-size:.9rem;opacity:.62}.search-input{width:100%;padding-left:38px!important}.routes-table-wrap{overflow:auto;border:1px solid var(--line);border-radius:18px;background:#fff}.routes-table{width:100%;border-collapse:separate;border-spacing:0;min-width:950px}.routes-table th,.routes-table td{border-bottom:1px solid var(--line);padding:11px 10px;text-align:left;vertical-align:middle}.routes-table th{position:sticky;top:0;z-index:1;background:#f8fafc;color:#64748b;text-transform:uppercase;font-size:.72rem;letter-spacing:.04em}.routes-table tbody tr:last-child td{border-bottom:0}.route-row{cursor:pointer;transition:background .12s ease, box-shadow .12s ease}.route-row:hover{box-shadow:inset 4px 0 0 var(--accent);background:#f8fbff}.route-row.has-stock{background:#f0fdf4}.route-row.has-stock:hover{background:#dcfce7}.route-row.no-stock{background:#fff7ed}.route-row.no-stock:hover{background:#ffedd5}.route-main{display:flex;flex-direction:column;gap:2px}.route-main b{font-size:1rem;color:#08264a}.route-main span{font-size:.78rem;color:var(--muted)}.user-cell b{display:block}.user-cell span{display:block;font-size:.78rem;color:var(--muted)}.status-chip{display:inline-flex;align-items:center;gap:5px;border-radius:999px;padding:5px 9px;font-size:.74rem;font-weight:900;white-space:nowrap}.status-stock{background:#dcfce7;color:#166534}.status-empty{background:#ffedd5;color:#c2410c}.status-idle{background:#fef3c7;color:#92400e}.pager{display:flex;justify-content:flex-end;align-items:center;gap:6px;margin-top:12px;flex-wrap:wrap}.pager button{border:1px solid #bcd3ef;background:#fff;color:#0b3b68;border-radius:10px;min-width:38px;height:38px;font-weight:850;cursor:pointer}.pager button.active{background:var(--accent);border-color:var(--accent);color:#fff}.pager button:disabled{opacity:.45;cursor:not-allowed}.mobile-route-card{display:none}
@media(max-width:700px){.routes-table-wrap{border:0;background:transparent;overflow:visible}.routes-table{min-width:0}.routes-table thead{display:none}.routes-table,.routes-table tbody,.routes-table tr,.routes-table td{display:block;width:100%}.routes-table tr{border:1px solid var(--line);border-radius:16px;margin-bottom:10px;padding:10px;box-shadow:0 8px 18px rgba(15,23,42,.06)}.routes-table td{border:0!important;padding:6px 4px!important;text-align:left!important}.routes-table td:before{content:attr(data-label);display:block;color:#64748b;font-size:.7rem;text-transform:uppercase;font-weight:850}.pager{justify-content:center}.grid-tools{align-items:stretch}.search-wrap{min-width:100%}}

</style>
</head>
<body>
<header class="topbar">
  <div class="topbar-inner">
    <div>
      <div class="sub">BReport · Planta Botella</div>
      <div class="title">Revisión de rutas de reparto</div>
      <div class="sub"><?=h($scopeCompanyName)?> · <?=h($scopeLocationName)?></div>
    </div>
    <div class="toolbar">
      <input class="input" type="date" id="workDate" value="<?=h($defaultWorkDate)?>">
      <button class="btn btn-primary" id="btnRefresh">Actualizar</button>
      <a class="btn btn-outline" href="almacen_botella.php">Volver a almacén</a>
    </div>
  </div>
</header>
<main class="page">
  <div id="alertBox"></div>
  <section class="kpis">
    <div class="kpi"><span>Rutas activas</span><strong id="kRoutes">0</strong></div>
    <div class="kpi"><span>Piezas en rutas</span><strong id="kPieces">0</strong></div>
    <div class="kpi"><span>Salidas del día</span><strong id="kOut">0</strong></div>
    <div class="kpi"><span>Artículos sin salida</span><strong id="kIdle">0</strong></div>
  </section>
  <section class="panel">
    <div class="panel-head">
      <div>
        <div class="panel-title">Estatus de existencias por ruta</div>
        <div class="muted">Selecciona una ruta para ver movimientos del día y asignar repartidor.</div>
      </div>
      <div class="badge" id="lastUpdate">Cargando...</div>
    </div>
    <div class="grid-tools">
      <div class="search-wrap">
        <span class="search-icon">🔎</span>
        <input class="input search-input" id="routeSearch" placeholder="Buscar por ruta, código o usuario repartidor...">
      </div>
      <div class="muted" id="pageInfo">0 rutas</div>
    </div>
    <div class="routes-table-wrap">
      <table class="routes-table">
        <thead>
          <tr>
            <th>Ruta</th>
            <th>Repartidor</th>
            <th class="text-end">Actual</th>
            <th class="text-end">Entradas</th>
            <th class="text-end">Salidas</th>
            <th class="text-end">Productos</th>
            <th>Estado</th>
            <th></th>
          </tr>
        </thead>
        <tbody id="routesGrid"></tbody>
      </table>
    </div>
    <div class="pager" id="routesPager"></div>
  </section>
</main>

<div class="modal" id="routeModal">
  <div class="modal-card">
    <div class="modal-head">
      <div>
        <div class="panel-title" id="modalTitle">Ruta</div>
        <div class="muted" id="modalSub">Movimientos del día</div>
      </div>
      <button class="x" onclick="closeModal('routeModal')">×</button>
    </div>
    <div class="modal-body">
      <div class="split">
        <div>
          <div id="routeProducts"></div>
          <h3 style="margin:18px 0 8px;font-size:1rem">Movimientos del día</h3>
          <div id="routeMoves"></div>
        </div>
        <aside class="assign-box">
          <div class="panel-title" style="font-size:1rem">Asignar repartidor</div>
          <div class="muted" style="margin:4px 0 12px">Solo aparecen usuarios con rol reparto/repartidor.</div>
          <label class="muted" style="font-weight:850;font-size:.82rem">Usuario repartidor</label>
          <select class="select" id="assignUser" style="width:100%;margin-top:5px"></select>
          <label class="muted" style="font-weight:850;font-size:.82rem;margin-top:10px;display:block">Notas</label>
          <input class="input" id="assignNotes" style="width:100%;margin-top:5px" placeholder="Opcional">
          <button class="btn btn-dark" style="width:100%;margin-top:12px" id="btnAssign">Guardar asignación</button>
          <div id="currentAssign" class="notice" style="margin-top:12px"></div>
        </aside>
      </div>
    </div>
    <div class="modal-foot"><button class="btn" onclick="closeModal('routeModal')">Cerrar</button></div>
  </div>
</div>
<script>
const CSRF = <?=json_encode($csrf)?>;
let ROUTES = [];
let USERS = [];
let CURRENT_ROUTE = null;
let ROUTE_PAGE = 1;
const ROUTE_PAGE_SIZE = 15;
let ROUTE_FILTER = '';
function n(v){return Number(v||0).toLocaleString('es-MX',{maximumFractionDigits:0});}
function esc(s){return String(s??'').replace(/[&<>\"]/g,m=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'}[m]));}
function alertMsg(msg,type='ok'){document.getElementById('alertBox').innerHTML=`<div class="notice" style="margin-bottom:12px;${type==='danger'?'background:#fef2f2;border-color:#fecaca;color:#991b1b':''}">${esc(msg)}</div>`;setTimeout(()=>{document.getElementById('alertBox').innerHTML=''},5000);}
function workDate(){return encodeURIComponent(document.getElementById('workDate').value || '<?=$defaultWorkDate?>');}
async function api(action, opts={}){const res=await fetch(`?action=${action}&work_date=${workDate()}`,{headers:{'Content-Type':'application/json','X-CSRF':CSRF},...opts});const data=await res.json().catch(()=>({ok:false,error:'Respuesta inválida'}));if(!data.ok)throw new Error(data.error||'Error');return data;}
function renderUsers(selectedId){const sel=document.getElementById('assignUser');sel.innerHTML='<option value="">Selecciona...</option>'+USERS.map(u=>`<option value="${u.id}" ${Number(u.id)===Number(selectedId)?'selected':''}>${esc(u.full_name||u.username)} · ${esc(u.username)}</option>`).join('');}
function filteredRoutes(){
  const q = ROUTE_FILTER.trim().toLowerCase();
  if(!q) return ROUTES;
  return ROUTES.filter(r=>{
    const a = r.assignment || {};
    const txt = [
      r.warehouse_code, r.route_code, r.route_name, r.driver_name, r.vehicle_unit,
      a.full_name, a.username
    ].map(x=>String(x||'').toLowerCase()).join(' ');
    return txt.includes(q);
  });
}
function renderPager(totalRows){
  const pager=document.getElementById('routesPager');
  const pages=Math.max(1, Math.ceil(totalRows / ROUTE_PAGE_SIZE));
  if(ROUTE_PAGE > pages) ROUTE_PAGE = pages;
  if(totalRows <= ROUTE_PAGE_SIZE){pager.innerHTML='';return;}
  let html=`<button ${ROUTE_PAGE<=1?'disabled':''} onclick="goPage(${ROUTE_PAGE-1})">‹</button>`;
  const start=Math.max(1, ROUTE_PAGE-2);
  const end=Math.min(pages, ROUTE_PAGE+2);
  if(start>1){html+=`<button onclick="goPage(1)">1</button>`; if(start>2) html+=`<button disabled>…</button>`;}
  for(let i=start;i<=end;i++) html+=`<button class="${i===ROUTE_PAGE?'active':''}" onclick="goPage(${i})">${i}</button>`;
  if(end<pages){if(end<pages-1) html+=`<button disabled>…</button>`; html+=`<button onclick="goPage(${pages})">${pages}</button>`;}
  html+=`<button ${ROUTE_PAGE>=pages?'disabled':''} onclick="goPage(${ROUTE_PAGE+1})">›</button>`;
  pager.innerHTML=html;
}
function goPage(page){ROUTE_PAGE=page;renderRoutes();}
function routeStatus(r){
  const stock=Number(r.current_pieces||0);
  const idle=Number(r.idle_products||0);
  if(stock<=0) return '<span class="status-chip status-empty">🟠 Sin carga</span>';
  if(idle>0) return `<span class="status-chip status-idle">⚠ ${idle} sin salida</span>`;
  return '<span class="status-chip status-stock">🟢 Con existencia</span>';
}
function renderRoutes(){
  let totalPieces=0,totalOut=0,totalIdle=0;
  ROUTES.forEach(r=>{totalPieces+=Number(r.current_pieces||0);totalOut+=Number(r.day_out||0);totalIdle+=Number(r.idle_products||0);});
  document.getElementById('kRoutes').textContent=n(ROUTES.length);
  document.getElementById('kPieces').textContent=n(totalPieces);
  document.getElementById('kOut').textContent=n(totalOut);
  document.getElementById('kIdle').textContent=n(totalIdle);
  const body=document.getElementById('routesGrid');
  const rows=filteredRoutes();
  const totalPages=Math.max(1,Math.ceil(rows.length/ROUTE_PAGE_SIZE));
  if(ROUTE_PAGE>totalPages) ROUTE_PAGE=totalPages;
  const start=(ROUTE_PAGE-1)*ROUTE_PAGE_SIZE;
  const pageRows=rows.slice(start,start+ROUTE_PAGE_SIZE);
  document.getElementById('pageInfo').textContent=`${rows.length} ruta(s) · página ${ROUTE_PAGE} de ${totalPages}`;
  if(!rows.length){body.innerHTML='<tr><td colspan="8"><div class="empty">No hay rutas que coincidan con la búsqueda.</div></td></tr>';renderPager(0);return;}
  body.innerHTML=pageRows.map(r=>{
    const a=r.assignment||null;
    const hasStock=Number(r.current_pieces||0)>0;
    return `<tr class="route-row ${hasStock?'has-stock':'no-stock'}" onclick="openRoute(${Number(r.route_no)})">
      <td data-label="Ruta"><div class="route-main"><b>${esc(r.warehouse_code)}</b><span>${esc(r.route_code||'')} ${esc(r.route_name||'')}</span></div></td>
      <td data-label="Repartidor"><div class="user-cell"><b>${a?esc(a.full_name||a.username):'Sin asignar'}</b><span>${a?esc(a.username||''):'Pendiente de asignar'}</span></div></td>
      <td data-label="Actual" class="text-end"><b>${n(r.current_pieces)}</b> pz</td>
      <td data-label="Entradas" class="text-end text-ok">${n(r.day_in)}</td>
      <td data-label="Salidas" class="text-end text-danger">${n(r.day_out)}</td>
      <td data-label="Productos" class="text-end"><b>${n(r.products_count||0)}</b></td>
      <td data-label="Estado">${routeStatus(r)}</td>
      <td data-label="Acción" class="text-end"><button class="btn btn-outline" onclick="event.stopPropagation();openRoute(${Number(r.route_no)})">Ver detalle</button></td>
    </tr>`;
  }).join('');
  renderPager(rows.length);
}
async function loadAll(){try{document.body.classList.add('loading');const data=await api('bootstrap');ROUTES=data.routes||[];USERS=data.users||[];document.getElementById('lastUpdate').textContent='Actualizado '+(data.now||'');renderRoutes();}catch(e){alertMsg(e.message,'danger')}finally{document.body.classList.remove('loading');}}
async function openRoute(routeNo){try{CURRENT_ROUTE=routeNo;const data=await api(`route_detail&route_no=${routeNo}`);const r=data.route;document.getElementById('modalTitle').textContent=`${r.warehouse_code} · ${r.route_code||''}`;document.getElementById('modalSub').textContent=`${r.route_name||''} · Día operativo ${data.work_date}`;
 renderUsers(r.assignment?.user_id||'');document.getElementById('assignNotes').value='';document.getElementById('currentAssign').innerHTML=r.assignment?`Actual: <b>${esc(r.assignment.full_name||r.assignment.username)}</b><br><span class="muted">Asignado: ${esc(r.assignment.assigned_at||'')}</span>`:'Ruta sin repartidor asignado.';
 renderProducts(r.products_rows||[]);renderMoves(data.moves||[]);openModal('routeModal');}catch(e){alertMsg(e.message,'danger')}}
function renderProducts(rows){const el=document.getElementById('routeProducts');if(!rows.length){el.innerHTML='<div class="empty">La ruta no tiene existencia registrada.</div>';return;}el.innerHTML=`<h3 style="margin:0 0 8px;font-size:1rem">Existencia actual por artículo</h3><table class="table"><thead><tr><th>Artículo</th><th class="text-end">Actual</th><th class="text-end">Entradas</th><th class="text-end">Salidas</th><th>Estado</th></tr></thead><tbody>${rows.map(x=>`<tr><td data-label="Artículo"><b>${esc(x.sku)}</b><br><span class="muted">${esc(x.name)}</span></td><td data-label="Actual" class="text-end"><b>${n(x.current_qty)}</b></td><td data-label="Entradas" class="text-end text-ok">${n(x.day_in)}</td><td data-label="Salidas" class="text-end text-danger">${n(x.day_out)}</td><td data-label="Estado">${x.no_movement?'<span class="badge badge-warn">Sin salida hoy</span>':'<span class="badge badge-ok">Con movimiento</span>'}</td></tr>`).join('')}</tbody></table>`;}
function renderMoves(rows){const el=document.getElementById('routeMoves');if(!rows.length){el.innerHTML='<div class="empty">No se detectaron movimientos en el día operativo.</div>';return;}el.innerHTML=`<table class="table"><thead><tr><th>Hora</th><th>Artículo</th><th>Tipo</th><th class="text-end">Cantidad</th><th>Usuario</th><th>Referencia</th></tr></thead><tbody>${rows.map(m=>`<tr><td data-label="Hora">${esc(m.created_at||'')}</td><td data-label="Artículo"><b>${esc(m.sku)}</b><br><span class="muted">${esc(m.name)}</span></td><td data-label="Tipo"><span class="badge ${m.move_type==='OUT'?'badge-warn':'badge-ok'}">${esc(m.move_type)}</span></td><td data-label="Cantidad" class="text-end"><b>${n(m.qty)}</b></td><td data-label="Usuario">${esc(m.user_name||m.username||'')}</td><td data-label="Referencia">${esc(m.ref_type||'')} #${esc(m.ref_id||'')}</td></tr>`).join('')}</tbody></table>`;}
async function saveAssignment(){try{if(!CURRENT_ROUTE)throw new Error('Selecciona una ruta.');const userId=document.getElementById('assignUser').value;if(!userId)throw new Error('Selecciona un usuario repartidor.');const data=await api('assign_user',{method:'POST',body:JSON.stringify({route_no:CURRENT_ROUTE,user_id:userId,notes:document.getElementById('assignNotes').value})});alertMsg(data.message);await loadAll();await openRoute(CURRENT_ROUTE);}catch(e){alertMsg(e.message,'danger')}}
function openModal(id){document.getElementById(id).classList.add('show');document.body.style.overflow='hidden';}
function closeModal(id){document.getElementById(id).classList.remove('show');document.body.style.overflow='';}
document.getElementById('btnRefresh').addEventListener('click',loadAll);
document.getElementById('workDate').addEventListener('change',loadAll);
document.getElementById('btnAssign').addEventListener('click',saveAssignment);
document.getElementById('routeSearch').addEventListener('input',e=>{ROUTE_FILTER=e.target.value;ROUTE_PAGE=1;renderRoutes();});
loadAll();
</script>
</body>
</html>
