<?php
declare(strict_types=1);
session_start();
header('Content-Type: text/html; charset=utf-8');
error_reporting(E_ALL);
ini_set('display_errors', '1');

if (empty($_SESSION['vm_cockpit_token'])) {
    $_SESSION['vm_cockpit_token'] = bin2hex(random_bytes(16));
}
$TOKEN = $_SESSION['vm_cockpit_token'];

$ROOTS = [
    'home' => [
        'icon' => '🧰',
        'title' => 'HOME / SCRIPTS',
        'path' => '/home/gaston',
        'desc' => 'Scripts, .sh, herramientas, inventarios y experimentos sueltos.',
        'web' => false
    ],
    'web' => [
        'icon' => '🌐',
        'title' => 'HTML / PROYECTOS',
        'path' => '/var/www/html',
        'desc' => 'Sistemas web, juegos, paneles, PWAs y proyectos publicados.',
        'web' => true
    ],
    'db' => [
        'icon' => '🗃️',
        'title' => 'BASES / MARIADB',
        'path' => '',
        'desc' => 'Bases de datos, tablas, tamaños, columnas y muestra rápida.',
        'web' => false
    ],
];

function e(string $v): string { return htmlspecialchars($v, ENT_QUOTES, 'UTF-8'); }
function starts_with(string $h, string $n): bool { return substr($h, 0, strlen($n)) === $n; }

function format_bytes(int|float $bytes): string {
    $units = ['B','KB','MB','GB','TB']; $i = 0; $value = (float)$bytes;
    while ($value >= 1024 && $i < count($units) - 1) { $value /= 1024; $i++; }
    return number_format($value, $i === 0 ? 0 : 1, ',', '.') . ' ' . $units[$i];
}

function normalize_rel_path(string $path): string {
    $path = str_replace('\\', '/', trim($path));
    $path = preg_replace('#/+#', '/', $path);
    $path = trim($path, '/');
    if ($path === '') return '';
    $safe = [];
    foreach (explode('/', $path) as $part) {
        if ($part === '' || $part === '.') continue;
        if ($part === '..') { array_pop($safe); continue; }
        $safe[] = $part;
    }
    return implode('/', $safe);
}

function safe_join(string $base, string $rel): string {
    $rel = normalize_rel_path($rel);
    return $rel === '' ? $base : rtrim($base, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . str_replace('/', DIRECTORY_SEPARATOR, $rel);
}

function is_inside_base(string $base, string $path): bool {
    $baseReal = realpath($base); $pathReal = realpath($path);
    if ($baseReal === false || $pathReal === false) return false;
    $baseReal = rtrim(str_replace('\\', '/', $baseReal), '/') . '/';
    $pathReal = rtrim(str_replace('\\', '/', $pathReal), '/');
    return starts_with($pathReal . (is_dir($pathReal) ? '/' : ''), $baseReal);
}

function icon_for(string $name, bool $isDir): string {
    if ($isDir) return '📁';
    $lower = strtolower($name);
    $ext = strtolower(pathinfo($name, PATHINFO_EXTENSION));
    if (str_contains($lower, 'index')) return '🏠';
    if (str_contains($lower, 'panel')) return '🎛️';
    if (str_contains($lower, 'scanner')) return '🔎';
    if (str_contains($lower, 'config')) return '⚙️';
    if (str_contains($lower, 'clima')) return '🌤️';
    if (str_contains($lower, 'spectrum')) return '🎚️';
    if (str_contains($lower, 'chat')) return '💬';
    if (str_contains($lower, 'juego') || str_contains($lower, 'game')) return '🎮';
    $map = [
        'php'=>'🐘','html'=>'🌐','htm'=>'🌐','js'=>'📜','css'=>'🎨','json'=>'🧩',
        'sh'=>'⚡','py'=>'🐍','sql'=>'🗃️','txt'=>'📄','md'=>'📝','log'=>'📋',
        'png'=>'🖼️','jpg'=>'🖼️','jpeg'=>'🖼️','gif'=>'🖼️','svg'=>'🖼️','webp'=>'🖼️',
        'zip'=>'📦','rar'=>'📦','7z'=>'📦','gz'=>'📦','tar'=>'📦',
        'mp3'=>'🎵','wav'=>'🎵','mp4'=>'🎥','webm'=>'🎥','pdf'=>'📕',
        'ini'=>'⚙️','conf'=>'⚙️','env'=>'🔐'
    ];
    return $map[$ext] ?? '🔗';
}

function quick_dir_stats(string $dir, bool $showHidden): array {
    $folders = 0; $files = 0; $entry = '';
    try {
        foreach (scandir($dir) ?: [] as $item) {
            if ($item === '.' || $item === '..') continue;
            if (!$showHidden && starts_with($item, '.')) continue;
            $full = $dir . DIRECTORY_SEPARATOR . $item;
            if (is_dir($full)) $folders++;
            elseif (is_file($full)) {
                $files++;
                $lower = strtolower($item);
                if ($entry === '' && in_array($lower, ['index.php','index.html','panel.php','panel.html','app.php','home.php'], true)) $entry = $item;
            }
        }
    } catch (Throwable $ex) {}
    return ['folders' => $folders, 'files' => $files, 'entry' => $entry];
}

function file_desc(string $name, bool $isDir): string {
    if ($isDir) return 'Carpeta. Entrá con Hurgar para ver la siguiente capa.';
    $ext = strtoupper(pathinfo($name, PATHINFO_EXTENSION));
    return $ext === '' ? 'Archivo sin extensión.' : 'Archivo ' . $ext . '.';
}

function build_file_url(string $rel): string { return '/' . ltrim(str_replace('\\', '/', $rel), '/'); }

function list_layer(string $base, string $rel, bool $showHidden): array {
    $current = safe_join($base, $rel);
    if (!is_dir($current) || !is_inside_base($base, $current)) {
        return ['error' => 'Ruta inválida o inaccesible.', 'folders' => [], 'files' => [], 'current' => $base, 'rel' => ''];
    }
    $folders = []; $files = [];
    $items = scandir($current);
    if ($items === false) return ['error' => 'No se pudo leer la carpeta.', 'folders' => [], 'files' => [], 'current' => $current, 'rel' => $rel];

    foreach ($items as $item) {
        if ($item === '.' || $item === '..') continue;
        if (!$showHidden && starts_with($item, '.')) continue;
        $full = $current . DIRECTORY_SEPARATOR . $item;
        $itemRel = normalize_rel_path($rel === '' ? $item : $rel . '/' . $item);
        if (is_dir($full)) {
            $stats = quick_dir_stats($full, $showHidden);
            $folders[] = [
                'name' => $item, 'rel' => $itemRel, 'icon' => icon_for($item, true),
                'modified' => @date('Y-m-d H:i:s', filemtime($full)) ?: '-',
                'folders' => $stats['folders'], 'files' => $stats['files'], 'entry' => $stats['entry']
            ];
        } elseif (is_file($full) || is_link($full)) {
            $files[] = [
                'name' => $item, 'rel' => $itemRel, 'icon' => icon_for($item, false),
                'size' => @filesize($full) ?: 0,
                'modified' => @date('Y-m-d H:i:s', filemtime($full)) ?: '-',
                'ext' => strtolower(pathinfo($item, PATHINFO_EXTENSION))
            ];
        }
    }
    usort($folders, fn($a,$b) => strcasecmp($a['name'], $b['name']));
    usort($files, fn($a,$b) => strcasecmp($a['name'], $b['name']));
    return ['error' => '', 'folders' => $folders, 'files' => $files, 'current' => $current, 'rel' => $rel];
}

function delete_target(array $ROOTS, string $zone, string $rel): array {
    if (!isset($ROOTS[$zone]) || $zone === 'db') return [false, 'Zona inválida para borrar.'];
    $base = $ROOTS[$zone]['path']; $rel = normalize_rel_path($rel);
    if ($rel === '') return [false, 'No se puede borrar la raíz.'];
    $target = safe_join($base, $rel);
    if (!file_exists($target) && !is_link($target)) return [false, 'No existe el objetivo.'];
    if (!is_inside_base($base, $target)) return [false, 'Bloqueado: fuera de raíz permitida.'];

    if (is_file($target) || is_link($target)) {
        if (@unlink($target)) return [true, 'Archivo eliminado: ' . $rel];
        return [false, 'No pude borrar el archivo. Permisos insuficientes o archivo bloqueado.'];
    }
    if (is_dir($target)) {
        $items = array_values(array_diff(scandir($target) ?: [], ['.', '..']));
        if (count($items) > 0) return [false, 'La carpeta no está vacía. Por seguridad no borro carpetas con contenido.'];
        if (@rmdir($target)) return [true, 'Carpeta vacía eliminada: ' . $rel];
        return [false, 'No pude borrar la carpeta. Permisos insuficientes.'];
    }
    return [false, 'Tipo de archivo no soportado.'];
}

function db_connect(): ?mysqli {
    if (!class_exists('mysqli')) return null;
    $host = getenv('MYSQL_HOST') ?: 'localhost';
    $user = getenv('MYSQL_USER') ?: 'vm_dash';
$pass = getenv('MYSQL_PASS') ?: 'vm_dash123';
    $mysqli = @new mysqli($host, $user, $pass);
    if ($mysqli->connect_errno) return null;
    $mysqli->set_charset('utf8mb4');
    return $mysqli;
}
function db_quote_ident(string $name): string { return '`' . str_replace('`', '``', $name) . '`'; }
function db_list(mysqli $db): array {
    $sql = "SELECT s.schema_name AS db, ROUND(COALESCE(SUM(t.data_length + t.index_length),0)/1024/1024,2) AS mb, COUNT(t.table_name) AS tables_count
            FROM information_schema.schemata s
            LEFT JOIN information_schema.tables t ON t.table_schema = s.schema_name
            GROUP BY s.schema_name
            ORDER BY mb DESC, s.schema_name ASC";
    $out = []; $res = $db->query($sql);
    if ($res) while ($row = $res->fetch_assoc()) $out[] = $row;
    return $out;
}
function db_tables(mysqli $db, string $dbName): array {
    $safe = $db->real_escape_string($dbName);
    $sql = "SELECT table_name, table_rows, engine, ROUND((data_length + index_length)/1024/1024,2) AS mb
            FROM information_schema.tables
            WHERE table_schema = '{$safe}'
            ORDER BY mb DESC, table_name ASC";
    $out = []; $res = $db->query($sql);
    if ($res) while ($row = $res->fetch_assoc()) $out[] = $row;
    return $out;
}
function db_columns(mysqli $db, string $dbName, string $table): array {
    $out = []; $q = "SHOW FULL COLUMNS FROM " . db_quote_ident($dbName) . "." . db_quote_ident($table);
    $res = $db->query($q);
    if ($res) while ($row = $res->fetch_assoc()) $out[] = $row;
    return $out;
}
function db_sample(mysqli $db, string $dbName, string $table, int $limit = 20): array {
    $out = ['cols' => [], 'rows' => []];
    $q = "SELECT * FROM " . db_quote_ident($dbName) . "." . db_quote_ident($table) . " LIMIT " . max(1, min(50, $limit));
    $res = $db->query($q);
    if ($res) {
        while ($field = $res->fetch_field()) $out['cols'][] = $field->name;
        while ($row = $res->fetch_assoc()) $out['rows'][] = $row;
    }
    return $out;
}

$notice = ''; $error = '';
if (($_POST['action'] ?? '') === 'delete') {
    if (!hash_equals($TOKEN, (string)($_POST['token'] ?? ''))) $error = 'Token inválido. Recargá la página.';
    else {
        [$ok, $msg] = delete_target($ROOTS, (string)($_POST['zone'] ?? ''), (string)($_POST['rel'] ?? ''));
        if ($ok) $notice = $msg; else $error = $msg;
    }
}

$zone = (string)($_GET['zone'] ?? '');
if (!isset($ROOTS[$zone])) $zone = '';
$showHidden = isset($_GET['hidden']) && $_GET['hidden'] === '1';
$currentRel = normalize_rel_path((string)($_GET['path'] ?? ''));
$dbName = (string)($_GET['db'] ?? '');
$tableName = (string)($_GET['table'] ?? '');

function zone_url(string $zone, string $path = '', bool $hidden = false): string {
    $q = ['zone' => $zone];
    if ($path !== '') $q['path'] = $path;
    if ($hidden) $q['hidden'] = '1';
    return 'index.php?' . http_build_query($q);
}
function db_url(?string $db = null, ?string $table = null): string {
    $q = ['zone' => 'db'];
    if ($db !== null && $db !== '') $q['db'] = $db;
    if ($table !== null && $table !== '') $q['table'] = $table;
    return 'index.php?' . http_build_query($q);
}
?>
<!doctype html>
<html lang="es">
<head>
<meta charset="utf-8">
<title>La Improbable Boosted 2.0 · GastonR AI Solutions · ROMU</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
:root{--bg:#03060c;--bg2:#07111f;--panel:#0b1422;--line:#1d3964;--line2:#2c66ad;--text:#edf6ff;--muted:#8ea9ca;--blue:#3aa6ff;--blue2:#00d9ff;--green:#25d79b;--warn:#ffd34d;--bad:#ff5570;--shadow:0 0 0 1px rgba(58,166,255,.13),0 18px 42px rgba(0,0,0,.38)}
*{box-sizing:border-box}html{scroll-behavior:smooth}body{margin:0;min-height:100vh;color:var(--text);font-family:Segoe UI,Arial,sans-serif;background:radial-gradient(circle at 18% 0%,rgba(0,144,255,.22),transparent 32%),radial-gradient(circle at 88% 8%,rgba(37,215,155,.10),transparent 25%),linear-gradient(180deg,var(--bg2),var(--bg))}
a{color:inherit}.wrap{max-width:1420px;margin:0 auto;padding:22px}.hero,.panel,.searchPanel{border:1px solid var(--line);border-radius:20px;background:linear-gradient(180deg,rgba(13,25,44,.96),rgba(6,10,18,.96));box-shadow:var(--shadow)}.hero{padding:24px;margin-bottom:18px}h1{margin:0 0 8px;font-size:31px}.sub{margin:0;color:var(--muted);line-height:1.5}.toolbar{display:flex;gap:12px;flex-wrap:wrap;margin-top:18px}
.btn,button.btn{appearance:none;border:1px solid var(--line2);border-radius:12px;padding:10px 14px;color:#fff;text-decoration:none;background:linear-gradient(180deg,rgba(45,113,210,.32),rgba(13,36,73,.28));font-weight:850;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;gap:8px;transition:.15s ease;font-size:14px}.btn:hover{transform:translateY(-1px);border-color:var(--blue);box-shadow:0 0 22px rgba(58,166,255,.18)}.btn.ok{border-color:rgba(37,215,155,.44);background:linear-gradient(180deg,rgba(37,215,155,.16),rgba(7,61,45,.25))}.btn.bad{border-color:rgba(255,85,112,.44);background:linear-gradient(180deg,rgba(255,85,112,.15),rgba(70,12,23,.24))}.btn.ghost{background:rgba(255,255,255,.03)}.btn.disabled{opacity:.55;pointer-events:none;border-color:rgba(255,255,255,.1)}
.notice{margin:0 0 18px;padding:13px 15px;border-radius:13px;background:rgba(37,215,155,.08);border:1px solid rgba(37,215,155,.32);color:#d9fff2}.notice.bad{background:rgba(255,85,112,.08);border-color:rgba(255,85,112,.32);color:#ffd7df}
.homeGrid{display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:18px}.door{min-height:230px;border:1px solid var(--line);border-radius:22px;padding:22px;background:linear-gradient(180deg,rgba(14,25,43,.98),rgba(7,11,19,.98));box-shadow:var(--shadow);text-decoration:none;display:flex;flex-direction:column;justify-content:space-between;transition:.18s ease}.door:hover{transform:translateY(-3px);border-color:var(--blue);box-shadow:0 20px 55px rgba(0,0,0,.45)}.doorIcon{font-size:44px;margin-bottom:12px}.doorTitle{font-size:22px;font-weight:950;color:#fff;margin-bottom:8px}.doorDesc{color:var(--muted);line-height:1.45}.doorPath{margin-top:16px;font-family:Consolas,monospace;font-size:12px;color:#99ceff;background:rgba(255,255,255,.04);border:1px solid rgba(255,255,255,.07);border-radius:10px;padding:10px;word-break:break-all}
.breadcrumb{display:flex;align-items:center;flex-wrap:wrap;gap:8px;margin-top:16px;padding:12px;border:1px solid rgba(255,255,255,.07);border-radius:14px;background:rgba(255,255,255,.03)}.crumb{color:#dceeff;text-decoration:none;border:1px solid rgba(58,166,255,.22);border-radius:999px;padding:7px 10px;background:rgba(58,166,255,.08);font-weight:800;font-size:13px}.sep{color:#5e789b}.stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:12px;margin-top:18px}.stat{border:1px solid rgba(255,255,255,.07);border-radius:14px;padding:14px;background:rgba(255,255,255,.03)}.stat .n{font-size:26px;font-weight:900;color:var(--blue2);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.stat .l{font-size:13px;color:var(--muted)}
.searchPanel{margin:0 0 18px;padding:14px;background:rgba(6,12,22,.62)}.searchGrid{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:12px}#search{width:100%;height:48px;border-radius:14px;border:1px solid var(--line);background:rgba(4,9,17,.9);color:#fff;padding:0 16px;outline:none;font-size:15px}#search:focus{border-color:var(--blue);box-shadow:0 0 22px rgba(58,166,255,.16)}.counter{min-width:145px;height:48px;border-radius:14px;border:1px solid var(--line);display:flex;align-items:center;justify-content:center;color:var(--blue2);font-weight:900;background:rgba(255,255,255,.03)}.filters{display:flex;gap:8px;flex-wrap:wrap;margin-top:12px}.filter{padding:8px 11px;border-radius:999px;border:1px solid var(--line);background:rgba(255,255,255,.03);color:#dceeff;cursor:pointer;font-weight:800;font-size:12px}.filter:hover,.filter.on{border-color:var(--blue);color:#fff;background:rgba(58,166,255,.14)}
.panel{margin-bottom:18px;overflow:hidden}.panelHead{padding:15px 18px;border-bottom:1px solid rgba(255,255,255,.07);display:flex;justify-content:space-between;gap:12px;align-items:center}.panelHead h2{margin:0;font-size:20px}.panelBody{padding:18px}.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(285px,1fr));gap:16px}.card{min-height:222px;border:1px solid var(--line);border-radius:18px;padding:16px;background:linear-gradient(180deg,rgba(14,25,43,.98),rgba(7,11,19,.98));box-shadow:var(--shadow);display:flex;flex-direction:column}.top{display:flex;gap:13px;align-items:flex-start;margin-bottom:10px}.icon{font-size:32px;line-height:1;filter:drop-shadow(0 0 6px rgba(58,166,255,.16))}.title{font-size:18px;font-weight:900;color:#fff;word-break:break-word}.desc{color:var(--muted);font-size:14px;line-height:1.42}.meta{display:grid;gap:6px;color:#a9bdd8;font-size:13px;margin-top:10px}.path{margin-top:10px;padding:10px 12px;color:#93c7ff;background:rgba(255,255,255,.03);border:1px solid rgba(255,255,255,.06);border-radius:10px;word-break:break-all;font-size:12px;font-family:Consolas,monospace}.actions{display:flex;gap:9px;flex-wrap:wrap;margin-top:auto;padding-top:14px}.actions .btn{padding:9px 11px;font-size:13px}.empty{border:1px dashed var(--line2);border-radius:16px;color:var(--muted);padding:30px;text-align:center}.tableWrap{overflow:auto}table{width:100%;border-collapse:collapse;font-size:13px}th,td{padding:10px;border-bottom:1px solid rgba(255,255,255,.07);text-align:left;vertical-align:top}th{color:#9ecbff;font-size:12px;text-transform:uppercase;letter-spacing:.5px}td{color:#dce8f8}tr:hover td{background:rgba(255,255,255,.025)}.mono{font-family:Consolas,monospace}.tag{display:inline-flex;padding:4px 8px;border-radius:999px;background:rgba(58,166,255,.12);border:1px solid rgba(58,166,255,.22);color:#badeff;font-weight:800;font-size:12px}
@media(max-width:760px){.wrap{padding:14px}h1{font-size:25px}.hero{padding:18px}.toolbar .btn{width:100%}.searchGrid{grid-template-columns:1fr}.actions .btn,.actions form{flex:1}.actions form .btn{width:100%}}
/* BOOSTED THEME - GASTONR AI SOLUTIONS / ROMU */
:root{
--bg:#070000;
--bg2:#140000;
--panel:#180202;
--line:#6f1616;
--line2:#ff3030;
--text:#fff3f3;
--muted:#ffb7b7;
--blue:#ff3b3b;
--blue2:#ff6b6b;
--green:#ff4d4d;
--warn:#ffd34d;
--bad:#ff5570;
--shadow:0 0 0 1px rgba(255,48,48,.22),0 18px 48px rgba(0,0,0,.55),0 0 38px rgba(255,0,0,.16);
}

body{
background:
radial-gradient(circle at 18% 0%,rgba(255,0,0,.34),transparent 33%),
radial-gradient(circle at 88% 8%,rgba(255,70,70,.16),transparent 28%),
linear-gradient(180deg,#160000,#030000)!important;
}

.hero,.panel,.searchPanel{
border-color:#7e1b1b!important;
background:linear-gradient(180deg,rgba(32,3,3,.96),rgba(8,0,0,.97))!important;
box-shadow:var(--shadow)!important;
}

.hero{
position:relative;
overflow:hidden;
}

.hero:before{
content:"CUEVA01 · BOOSTED · 19V · processor.ignore_ppc=1";
display:inline-flex;
margin-bottom:14px;
padding:8px 12px;
border-radius:999px;
border:1px solid rgba(255,48,48,.55);
background:rgba(255,0,0,.10);
color:#ffc7c7;
font-family:Consolas,monospace;
font-size:12px;
font-weight:900;
letter-spacing:.4px;
}

.hero:after{
content:"GASTONR AI SOLUTIONS · ROMU";
position:absolute;
right:22px;
top:22px;
padding:7px 11px;
border-radius:999px;
border:1px solid rgba(255,255,255,.12);
background:rgba(0,0,0,.22);
color:#ff6b6b;
font-size:12px;
font-weight:950;
letter-spacing:.5px;
}

h1{
color:#fff!important;
text-shadow:0 0 26px rgba(255,0,0,.46);
letter-spacing:-1px;
}

h1::first-letter{
color:#ff3b3b;
}

.sub{
color:#ffd6d6!important;
font-weight:650;
}

.door,.card{
border-color:#7e1b1b!important;
background:linear-gradient(180deg,rgba(30,4,4,.98),rgba(9,0,0,.98))!important;
box-shadow:var(--shadow)!important;
}

.door:hover,.card:hover{
border-color:#ff3030!important;
box-shadow:0 22px 60px rgba(0,0,0,.55),0 0 45px rgba(255,0,0,.28)!important;
}

.doorTitle,.title{
color:#fff!important;
text-shadow:0 0 18px rgba(255,0,0,.25);
}

.doorDesc,.desc,.meta,.sub{
color:#ffc1c1!important;
}

.doorPath,.path,#search,.counter,.stat,.breadcrumb{
background:rgba(30,0,0,.72)!important;
border-color:rgba(255,48,48,.28)!important;
color:#ffd0d0!important;
}

.btn,button.btn{
border-color:#ff3030!important;
background:linear-gradient(180deg,rgba(255,48,48,.24),rgba(75,0,0,.38))!important;
}

.btn:hover{
box-shadow:0 0 28px rgba(255,0,0,.34)!important;
}

.tag,.crumb,.filter{
border-color:rgba(255,48,48,.42)!important;
background:rgba(255,0,0,.13)!important;
color:#ffdada!important;
}

.stat .n{
color:#ff5a5a!important;
}
</style>
</head>
<body><div class="wrap">

<?php if ($notice !== ''): ?><div class="notice">✅ <?= e($notice) ?></div><?php endif; ?>
<?php if ($error !== ''): ?><div class="notice bad">❌ <?= e($error) ?></div><?php endif; ?>

<?php if ($zone === ''): ?>
<section class="hero">
<h1>LA IMPROBABLE BOOSTED 2.0</h1>
<p class="sub">GASTONR AI SOLUTIONS · ROMU · CUEVA01 liberada del cepo · i7 con turbo · Alto Alberdi operativo. Tres puertas y nada más: scripts, proyectos web y bases.</p>
</section>
<div class="homeGrid">
<?php foreach ($ROOTS as $key => $root): ?>
<a class="door" href="<?= $key === 'db' ? 'index.php?zone=db' : zone_url($key) ?>">
<div><div class="doorIcon"><?= e($root['icon']) ?></div><div class="doorTitle"><?= e($root['title']) ?></div><div class="doorDesc"><?= e($root['desc']) ?></div></div>
<div class="doorPath"><?= $key === 'db' ? 'MariaDB / MySQL' : e($root['path']) ?></div>
</a>
<?php endforeach; ?>
</div>

<?php elseif ($zone !== 'db'): ?>
<?php
$root = $ROOTS[$zone];
$layer = list_layer($root['path'], $currentRel, $showHidden);
if ($layer['error']) $error = $layer['error'];
$folders = $layer['folders']; $files = $layer['files'];
$parts = $currentRel === '' ? [] : explode('/', $currentRel);
$crumbs = []; $acc = '';
foreach ($parts as $part) { $acc = $acc === '' ? $part : $acc . '/' . $part; $crumbs[] = ['name' => $part, 'path' => $acc]; }
$parent = '';
if ($currentRel !== '') { $tmp = explode('/', $currentRel); array_pop($tmp); $parent = implode('/', $tmp); }
$total = count($folders) + count($files);
?>
<section class="hero">
<h1><?= e($root['icon'] . ' ' . $root['title']) ?></h1>
<p class="sub"><?= e($root['desc']) ?></p>
<div class="toolbar">
<a class="btn" href="index.php">🏠 Tres puertas</a>
<a class="btn ghost" href="<?= zone_url($zone) ?>">🌳 Raíz</a>
<?php if ($currentRel !== ''): ?><a class="btn ghost" href="<?= zone_url($zone, $parent, $showHidden) ?>">⬆️ Subir</a><?php endif; ?>
<a class="btn ghost" href="<?= zone_url($zone, $currentRel, !$showHidden) ?>"><?= $showHidden ? '🙈 Ocultar ocultos' : '👁️ Mostrar ocultos' ?></a>
</div>
<div class="breadcrumb">
<a class="crumb" href="<?= zone_url($zone) ?>"><?= e(basename($root['path'])) ?></a>
<?php foreach ($crumbs as $crumb): ?><span class="sep">/</span><a class="crumb" href="<?= zone_url($zone, $crumb['path'], $showHidden) ?>"><?= e($crumb['name']) ?></a><?php endforeach; ?>
</div>
<div class="stats">
<div class="stat"><div class="n"><?= count($folders) ?></div><div class="l">Carpetas en esta capa</div></div>
<div class="stat"><div class="n"><?= count($files) ?></div><div class="l">Archivos en esta capa</div></div>
<div class="stat"><div class="n"><?= $total ?></div><div class="l">Total mostrado</div></div>
<div class="stat"><div class="n"><?= $currentRel === '' ? 'ROOT' : e(basename($currentRel)) ?></div><div class="l">Nivel actual</div></div>
</div>
</section>

<section class="searchPanel">
<div class="searchGrid"><input id="search" type="search" placeholder="Buscar solo en esta capa..." autocomplete="off"><div class="counter"><span id="count"><?= $total ?></span> visibles</div></div>
<div class="filters">
<button class="filter on" data-filter="">Todo</button><button class="filter" data-filter="kind:folder">📁 Carpetas</button><button class="filter" data-filter="kind:file">📄 Archivos</button><button class="filter" data-filter=".php">🐘 PHP</button><button class="filter" data-filter=".sh">⚡ SH</button><button class="filter" data-filter=".js">📜 JS</button><button class="filter" data-filter=".json">🧩 JSON</button>
</div>
</section>

<section class="panel"><div class="panelHead"><h2>📁 Carpetas</h2><span><?= count($folders) ?></span></div><div class="panelBody">
<?php if (!$folders): ?><div class="empty">No hay carpetas en esta capa.</div><?php else: ?><div class="grid">
<?php foreach ($folders as $f): $hay = strtolower('kind:folder ' . $f['name'] . ' ' . $f['rel']); ?>
<article class="card" data-search="<?= e($hay) ?>">
<div class="top"><div class="icon"><?= e($f['icon']) ?></div><div><div class="title"><?= e($f['name']) ?></div><div class="desc"><?= e(file_desc($f['name'], true)) ?></div></div></div>
<div class="meta"><span>📁 Subcarpetas directas: <?= (int)$f['folders'] ?></span><span>📄 Archivos directos: <?= (int)$f['files'] ?></span><?php if ($f['entry'] !== ''): ?><span>🏠 Entrada: <?= e($f['entry']) ?></span><?php endif; ?><span>🕒 Modificado: <?= e($f['modified']) ?></span></div>
<div class="path"><?= e($f['rel']) ?>/</div>
<div class="actions">
<a class="btn" href="<?= zone_url($zone, $f['rel'], $showHidden) ?>">🕵️ Hurgar</a>
<?php if ($root['web']): ?><a class="btn ghost" href="<?= e(build_file_url($f['rel'])) ?>/" target="_blank">🚀 Abrir</a><?php endif; ?>
<form method="post" onsubmit="return confirm('¿Borrar carpeta vacía <?= e($f['name']) ?>? Si tiene contenido no se borra.');">
<input type="hidden" name="token" value="<?= e($TOKEN) ?>"><input type="hidden" name="action" value="delete"><input type="hidden" name="zone" value="<?= e($zone) ?>"><input type="hidden" name="rel" value="<?= e($f['rel']) ?>"><button class="btn bad" type="submit">🗑️ Borrar</button>
</form>
</div>
</article>
<?php endforeach; ?></div><?php endif; ?></div></section>

<section class="panel"><div class="panelHead"><h2>📄 Archivos</h2><span><?= count($files) ?></span></div><div class="panelBody">
<?php if (!$files): ?><div class="empty">No hay archivos en esta capa.</div><?php else: ?><div class="grid">
<?php foreach ($files as $f): $hay = strtolower('kind:file .' . $f['ext'] . ' ' . $f['name'] . ' ' . $f['rel']); ?>
<article class="card" data-search="<?= e($hay) ?>">
<div class="top"><div class="icon"><?= e($f['icon']) ?></div><div><div class="title"><?= e($f['name']) ?></div><div class="desc"><?= e(file_desc($f['name'], false)) ?></div></div></div>
<div class="meta"><span>💾 Tamaño: <?= e(format_bytes((int)$f['size'])) ?></span><span>🕒 Modificado: <?= e($f['modified']) ?></span></div>
<div class="path"><?= e($f['rel']) ?></div>
<div class="actions">
<?php if ($root['web']): ?><a class="btn" href="<?= e(build_file_url($f['rel'])) ?>" target="_blank">🚀 Abrir</a><?php endif; ?>
<form method="post" onsubmit="return confirm('¿Borrar archivo <?= e($f['name']) ?>? Esto sí borra el archivo real.');">
<input type="hidden" name="token" value="<?= e($TOKEN) ?>"><input type="hidden" name="action" value="delete"><input type="hidden" name="zone" value="<?= e($zone) ?>"><input type="hidden" name="rel" value="<?= e($f['rel']) ?>"><button class="btn bad" type="submit">🗑️ Borrar</button>
</form>
</div>
</article>
<?php endforeach; ?></div><?php endif; ?></div></section>

<?php else: ?>
<?php
$mysqli = db_connect();
$dbs = $mysqli ? db_list($mysqli) : [];
$selectedTables = ($mysqli && $dbName !== '') ? db_tables($mysqli, $dbName) : [];
$columns = ($mysqli && $dbName !== '' && $tableName !== '') ? db_columns($mysqli, $dbName, $tableName) : [];
$sample = ($mysqli && $dbName !== '' && $tableName !== '') ? db_sample($mysqli, $dbName, $tableName, 20) : ['cols'=>[], 'rows'=>[]];
$total = $dbName === '' ? count($dbs) : count($selectedTables);
?>
<section class="hero">
<h1>🗃️ BASES / MARIADB</h1>
<p class="sub">Mirar bases, hurgar tablas, ver columnas y muestra. Borrado de DB desactivado para evitar una tragedia bíblica.</p>
<div class="toolbar"><a class="btn" href="index.php">🏠 Tres puertas</a><a class="btn ghost" href="<?= db_url() ?>">🗃️ Bases</a><?php if ($dbName !== ''): ?><a class="btn ghost" href="<?= db_url($dbName) ?>">📋 Tablas de <?= e($dbName) ?></a><?php endif; ?></div>
<div class="breadcrumb"><a class="crumb" href="<?= db_url() ?>">MariaDB</a><?php if ($dbName !== ''): ?><span class="sep">/</span><a class="crumb" href="<?= db_url($dbName) ?>"><?= e($dbName) ?></a><?php endif; ?><?php if ($tableName !== ''): ?><span class="sep">/</span><a class="crumb" href="<?= db_url($dbName, $tableName) ?>"><?= e($tableName) ?></a><?php endif; ?></div>
<div class="stats"><div class="stat"><div class="n"><?= $mysqli ? 'OK' : 'NO' ?></div><div class="l">Conexión DB</div></div><div class="stat"><div class="n"><?= $total ?></div><div class="l"><?= $dbName === '' ? 'Bases visibles' : 'Tablas visibles' ?></div></div><div class="stat"><div class="n"><?= $dbName === '' ? 'ROOT' : e($dbName) ?></div><div class="l">Nivel actual</div></div></div>
</section>

<?php if (!$mysqli): ?>
<div class="notice bad">No pude conectar a MariaDB desde PHP con root sin password. Probá definir MYSQL_USER / MYSQL_PASS o usá el resumen por SSH.</div>
<?php elseif ($dbName === ''): ?>
<section class="searchPanel"><div class="searchGrid"><input id="search" type="search" placeholder="Buscar base..." autocomplete="off"><div class="counter"><span id="count"><?= count($dbs) ?></span> visibles</div></div></section>
<section class="panel"><div class="panelHead"><h2>🗃️ Bases</h2><span><?= count($dbs) ?></span></div><div class="panelBody tableWrap"><table><thead><tr><th>Base</th><th>Tamaño</th><th>Tablas</th><th>Acción</th></tr></thead><tbody>
<?php foreach ($dbs as $d): $name = (string)$d['db']; $system = in_array($name, ['mysql','information_schema','performance_schema','sys','phpmyadmin'], true); $hay = strtolower($name . ' ' . ($system ? 'system' : 'user')); ?>
<tr class="dbrow" data-search="<?= e($hay) ?>"><td><span class="tag"><?= $system ? '⚙️' : '🗃️' ?> <?= e($name) ?></span></td><td class="mono"><?= e((string)$d['mb']) ?> MB</td><td class="mono"><?= e((string)$d['tables_count']) ?></td><td><a class="btn" href="<?= db_url($name) ?>">🕵️ Hurgar tablas</a></td></tr>
<?php endforeach; ?>
</tbody></table></div></section>

<?php elseif ($tableName === ''): ?>
<section class="searchPanel"><div class="searchGrid"><input id="search" type="search" placeholder="Buscar tabla..." autocomplete="off"><div class="counter"><span id="count"><?= count($selectedTables) ?></span> visibles</div></div></section>
<section class="panel"><div class="panelHead"><h2>📋 Tablas de <?= e($dbName) ?></h2><span><?= count($selectedTables) ?></span></div><div class="panelBody tableWrap"><table><thead><tr><th>Tabla</th><th>Filas aprox</th><th>MB</th><th>Motor</th><th>Acción</th></tr></thead><tbody>
<?php foreach ($selectedTables as $t): $name = (string)$t['table_name']; $hay = strtolower($name . ' ' . ($t['engine'] ?? '')); ?>
<tr class="dbrow" data-search="<?= e($hay) ?>"><td><span class="tag">📄 <?= e($name) ?></span></td><td class="mono"><?= e((string)$t['table_rows']) ?></td><td class="mono"><?= e((string)$t['mb']) ?></td><td class="mono"><?= e((string)$t['engine']) ?></td><td><a class="btn" href="<?= db_url($dbName, $name) ?>">🕵️ Ver columnas/muestra</a></td></tr>
<?php endforeach; ?>
</tbody></table></div></section>

<?php else: ?>
<section class="panel"><div class="panelHead"><h2>🧬 Columnas: <?= e($dbName) ?> / <?= e($tableName) ?></h2><span><?= count($columns) ?></span></div><div class="panelBody tableWrap"><table><thead><tr><th>Campo</th><th>Tipo</th><th>Null</th><th>Key</th><th>Default</th><th>Extra</th></tr></thead><tbody>
<?php foreach ($columns as $c): ?><tr><td class="mono"><?= e((string)$c['Field']) ?></td><td class="mono"><?= e((string)$c['Type']) ?></td><td><?= e((string)$c['Null']) ?></td><td><?= e((string)$c['Key']) ?></td><td class="mono"><?= e((string)($c['Default'] ?? '')) ?></td><td><?= e((string)$c['Extra']) ?></td></tr><?php endforeach; ?>
</tbody></table></div></section>
<section class="panel"><div class="panelHead"><h2>🔬 Muestra de datos</h2><span><?= count($sample['rows']) ?> filas</span></div><div class="panelBody tableWrap">
<?php if (!$sample['rows']): ?><div class="empty">Sin filas para mostrar o sin permiso de SELECT.</div><?php else: ?>
<table><thead><tr><?php foreach ($sample['cols'] as $col): ?><th><?= e((string)$col) ?></th><?php endforeach; ?></tr></thead><tbody>
<?php foreach ($sample['rows'] as $row): ?><tr><?php foreach ($sample['cols'] as $col): $val = (string)($row[$col] ?? ''); if (strlen($val) > 90) $val = substr($val, 0, 90) . '...'; ?><td class="mono"><?= e($val) ?></td><?php endforeach; ?></tr><?php endforeach; ?>
</tbody></table>
<?php endif; ?></div></section>
<?php endif; ?>
<?php endif; ?>

</div>
<script>
(function(){
const input=document.getElementById('search');const count=document.getElementById('count');const cards=Array.from(document.querySelectorAll('.card,.dbrow'));const filters=Array.from(document.querySelectorAll('.filter'));let activeFilter='';
function applyFilter(){const q=(input?input.value:'').trim().toLowerCase();let visible=0;for(const card of cards){const hay=card.dataset.search||'';const okText=!q||hay.includes(q);const okFilter=!activeFilter||hay.includes(activeFilter);const ok=okText&&okFilter;card.style.display=ok?'':'none';if(ok)visible++;}if(count)count.textContent=visible;}
if(input)input.addEventListener('input',applyFilter);
filters.forEach(btn=>{btn.addEventListener('click',()=>{filters.forEach(x=>x.classList.remove('on'));btn.classList.add('on');activeFilter=(btn.dataset.filter||'').toLowerCase();applyFilter();});});
})();
</script>
</body></html>
