<?php
// ╔══════════════════════════════════════════════════════════════╗
// ║              GESTOR DE ARCHIVOS WEB  v1.1                   ║
// ╚══════════════════════════════════════════════════════════════╝
session_start();

define('BASE_DIR',    realpath(__DIR__));
define('CONFIG_FILE', BASE_DIR . DIRECTORY_SEPARATOR . 'config.php');
define('APP_TITLE',   'Acceso a Clientes');
define('MAX_UPLOAD',  200 * 1024 * 1024); // 200 MB

$HIDDEN = ['.', '..', 'index.php', 'config.php',
           '.htaccess', '.htpasswd', '.htgroups',
           '.well-known', '.git', '.env', '.DS_Store', 'Thumbs.db', 'secret'];

// ════════════════════════════════════════════════════════════
//  FUNCIONES UTILITARIAS
// ════════════════════════════════════════════════════════════
function h(string $s): string { return htmlspecialchars($s, ENT_QUOTES|ENT_HTML5, 'UTF-8'); }
function eu(string $s): string { return urlencode($s); }

function fmtSize(?int $b): string {
    if ($b === null) return '—';
    foreach (['B','KB','MB','GB'] as $u) {
        if ($b < 1024) return round($b, 1)." $u";
        $b = (int)($b / 1024);
    }
    return "$b TB";
}
function fmtDate(int $ts): string { return date('d/m/Y H:i', $ts); }

function csrf(): string {
    if (empty($_SESSION['csrf'])) $_SESSION['csrf'] = bin2hex(random_bytes(16));
    return $_SESSION['csrf'];
}
function checkCsrf(): void {
    if (!hash_equals($_SESSION['csrf'] ?? '', $_POST['_csrf'] ?? ''))
        { http_response_code(403); die('Token CSRF inválido.'); }
}
function isAdmin(): bool { return !empty($_SESSION['fm_admin']); }
function requireAdmin(): void {
    if (!isAdmin()) { http_response_code(403); die('Acceso denegado.'); }
}
function isPrivate(string $abs): bool {
    // La raíz NUNCA es "privada" (siempre tiene .htaccess de configuración del server).
    // "Privada" en este contexto = subcarpeta con .htaccess que marca acceso restringido.
    if ($abs === BASE_DIR) return false;
    return is_dir($abs) && file_exists($abs . DIRECTORY_SEPARATOR . '.htaccess');
}
function safeResolve(string $rel): ?string {
    $rel = preg_replace('#\.\.+#', '', $rel);
    $rel = ltrim(str_replace(['\\', '//'], '/', $rel), '/');
    $abs = realpath(BASE_DIR . DIRECTORY_SEPARATOR . str_replace('/', DIRECTORY_SEPARATOR, $rel));
    if (!$abs || strpos($abs . DIRECTORY_SEPARATOR, BASE_DIR . DIRECTORY_SEPARATOR) !== 0) return null;
    return $abs;
}
function rrmdir(string $path): void {
    $it  = new RecursiveDirectoryIterator($path, FilesystemIterator::SKIP_DOTS);
    $rit = new RecursiveIteratorIterator($it, RecursiveIteratorIterator::CHILD_FIRST);
    foreach ($rit as $f) $f->isDir() ? rmdir((string)$f) : unlink((string)$f);
    rmdir($path);
}

// ════ SISTEMA DE ICONOS SVG ════
function icon(string $name, int $size = 16, string $color = 'currentColor'): string {
    static $p = [
        'folder'      => '<path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/>',
        'folder-plus' => '<path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/><line x1="12" y1="11" x2="12" y2="17"/><line x1="9" y1="14" x2="15" y2="14"/>',
        'file'        => '<path d="M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z"/><polyline points="13 2 13 9 20 9"/>',
        'file-text'   => '<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/>',
        'archive'     => '<polyline points="21 8 21 21 3 21 3 8"/><rect x="1" y="3" width="22" height="5"/><line x1="10" y1="12" x2="14" y2="12"/>',
        'image'       => '<rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/><polyline points="21 15 16 10 5 21"/>',
        'code'        => '<polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/>',
        'database'    => '<ellipse cx="12" cy="5" rx="9" ry="3"/><path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3"/><path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5"/>',
        'monitor'     => '<rect x="2" y="3" width="20" height="14" rx="2"/><line x1="8" y1="21" x2="16" y2="21"/><line x1="12" y1="17" x2="12" y2="21"/>',
        'music'       => '<path d="M9 18V5l12-2v13"/><circle cx="6" cy="18" r="3"/><circle cx="18" cy="16" r="3"/>',
        'video'       => '<polygon points="23 7 16 12 23 17 23 7"/><rect x="1" y="5" width="15" height="14" rx="2"/>',
        'smartphone'  => '<rect x="5" y="2" width="14" height="20" rx="2"/><line x1="12" y1="18" x2="12.01" y2="18"/>',
        'lock'        => '<rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/>',
        'unlock'      => '<rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0 1 9.9-1"/>',
        'upload'      => '<polyline points="16 16 12 12 8 16"/><line x1="12" y1="12" x2="12" y2="21"/><path d="M20.39 18.39A5 5 0 0 0 18 9h-1.26A8 8 0 1 0 3 16.3"/>',
        'download'    => '<polyline points="8 17 12 21 16 17"/><line x1="12" y1="12" x2="12" y2="21"/><path d="M20.88 18.09A5 5 0 0 0 18 9h-1.26A8 8 0 1 0 3 16.29"/>',
        'trash'       => '<polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2"/>',
        'edit'        => '<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/>',
        'users'       => '<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/>',
        'user-plus'   => '<path d="M16 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="8.5" cy="7" r="4"/><line x1="20" y1="8" x2="20" y2="14"/><line x1="23" y1="11" x2="17" y2="11"/>',
        'user-x'      => '<path d="M16 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="8.5" cy="7" r="4"/><line x1="18" y1="8" x2="23" y2="13"/><line x1="23" y1="8" x2="18" y2="13"/>',
        'key'         => '<path d="M21 2l-2 2m-7.61 7.61a5.5 5.5 0 1 1-7.778 7.778 5.5 5.5 0 0 1 7.777-7.777zm0 0L15.5 7.5m0 0l3 3L22 7l-3-3m-3.5 3.5L19 4"/>',
        'log-out'     => '<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/><polyline points="16 17 21 12 16 7"/><line x1="21" y1="12" x2="9" y2="12"/>',
        'plus'        => '<line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/>',
        'home'        => '<path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/>',
        'chevron-r'   => '<polyline points="9 18 15 12 9 6"/>',
        'check'       => '<polyline points="20 6 9 17 4 12"/>',
        'x'           => '<line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/>',
        'shield'      => '<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/>',
        'eye'         => '<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/>',
        'globe'       => '<circle cx="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/>',
        'refresh'     => '<polyline points="23 4 23 10 17 10"/><polyline points="1 20 1 14 7 14"/><path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"/>',
        'alert'       => '<circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/>',
    ];
    $path = $p[$name] ?? $p['file'];
    return '<svg xmlns="http://www.w3.org/2000/svg" width="'.$size.'" height="'.$size.'"'
         . ' viewBox="0 0 24 24" fill="none" stroke="'.$color.'"'
         . ' stroke-width="2" stroke-linecap="round" stroke-linejoin="round"'
         . ' style="display:inline-block;vertical-align:middle;flex-shrink:0">'
         . $path . '</svg>';
}

function fileIcon(string $name, bool $isDir, int $size = 18, string $color = 'currentColor'): string {
    if ($isDir) return icon('folder', $size, $color);
    static $m = [
        'pdf'=>'file-text','doc'=>'file-text','docx'=>'file-text','odt'=>'file-text',
        'xls'=>'file-text','xlsx'=>'file-text','csv'=>'file-text',
        'ppt'=>'file-text','pptx'=>'file-text',
        'zip'=>'archive','rar'=>'archive','7z'=>'archive','gz'=>'archive','tar'=>'archive',
        'jpg'=>'image','jpeg'=>'image','png'=>'image','gif'=>'image','svg'=>'image','webp'=>'image',
        'mp4'=>'video','avi'=>'video','mkv'=>'video','mov'=>'video','wmv'=>'video',
        'mp3'=>'music','wav'=>'music','ogg'=>'music','flac'=>'music',
        'php'=>'code','js'=>'code','ts'=>'code','css'=>'code','html'=>'code','htm'=>'code',
        'py'=>'code','rb'=>'code','sh'=>'code',
        'mdb'=>'database','sql'=>'database','db'=>'database','sqlite'=>'database',
        'exe'=>'monitor','msi'=>'monitor','bat'=>'monitor','cmd'=>'monitor',
        'apk'=>'smartphone',
    ];
    $ext = strtolower(pathinfo($name, PATHINFO_EXTENSION));
    return icon($m[$ext] ?? 'file', $size, $color);
}

// ════ GESTIÓN DE .htpasswd ════
function htFile(): string {
    return BASE_DIR . DIRECTORY_SEPARATOR . 'secret' . DIRECTORY_SEPARATOR . '.htpasswd';
}
function htRead(): array {
    if (!file_exists(htFile())) return [];
    $users = [];
    foreach (file(htFile(), FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) as $line) {
        $parts = explode(':', $line, 2);
        if (count($parts) === 2) $users[trim($parts[0])] = trim($parts[1]);
    }
    return $users;
}
function htWrite(array $users): void {
    $dir = dirname(htFile());
    if (!is_dir($dir)) @mkdir($dir, 0755, true);
    $out = '';
    foreach ($users as $u => $h) $out .= trim($u) . ':' . trim($h) . "\n";
    file_put_contents(htFile(), $out);
    @chmod(htFile(), 0644);
}
function htSetUser(string $u, string $pass): void {
    $users = htRead();
    // Bcrypt cost=10 (compatible con todas las versiones de Apache 2.4+),
    // password_hash() genera $2y$ que es exactamente lo que Apache espera.
    $users[$u] = password_hash($pass, PASSWORD_BCRYPT, ['cost' => 10]);
    htWrite($users);
}
function htDelUser(string $u): void {
    $users = htRead(); unset($users[$u]); htWrite($users);
}
function folderUser(string $absDir): ?string {
    $ht = $absDir . DIRECTORY_SEPARATOR . '.htaccess';
    if (!file_exists($ht)) return null;
    return preg_match('/^\s*Require\s+user\s+(\S+)/im', file_get_contents($ht), $m) ? $m[1] : null;
}
function folderSetUser(string $absDir, string $u): bool {
    // Carpeta privada con auth: el cliente autenticado DEBE poder ver el listing
    // (mod_autoindex). FancyIndexing hace el listado mucho más prolijo.
    $content = "AuthUserFile /home/alpha2000/public_html/sistemas/secret/.htpasswd\n"
             . "AuthName \"ALPHA2000\"\n"
             . "AuthType Basic\n"
             . "Require user $u\n"
             . "Options +Indexes\n"
             . "IndexOptions FancyIndexing HTMLTable VersionSort FoldersFirst IgnoreCase NameWidth=* SuppressDescription SuppressIcon\n"
             . "IndexIgnore .htaccess .htpasswd .DS_Store Thumbs.db\n";
    $path = $absDir . DIRECTORY_SEPARATOR . '.htaccess';
    $ok = @file_put_contents($path, $content) !== false;
    if ($ok) @chmod($path, 0644);
    return $ok;
}
function folderRemoveUser(string $absDir): bool {
    // "Sin contraseña" = carpeta pública navegable.
    // +Indexes para que el listado funcione (sin esto + sin index = 403).
    // El .htaccess se mantiene para que la carpeta siga apareciendo en el panel
    // de Permisos y se le pueda reasignar un usuario fácilmente más adelante.
    $path = $absDir . DIRECTORY_SEPARATOR . '.htaccess';
    $content = "Options +Indexes\n"
             . "IndexOptions FancyIndexing HTMLTable VersionSort FoldersFirst IgnoreCase NameWidth=* SuppressDescription SuppressIcon\n"
             . "IndexIgnore .htaccess .htpasswd .DS_Store Thumbs.db\n";
    $ok = @file_put_contents($path, $content) !== false;
    if ($ok) @chmod($path, 0644);
    return $ok;
}
function htUserFolders(): array {
    $result = [];
    foreach (allPrivateDirs() as $dir) {
        if ($dir['user']) $result[$dir['user']][] = $dir['name'];
    }
    return $result;
}
/** Returns all dirs with .htaccess, up to 2 levels deep */
function allPrivateDirs(): array {
    $skip = ['secret', '.git', '.well-known'];
    $result = [];
    $scan = function(string $absBase, string $relBase, int $depth) use (&$scan, $skip, &$result) {
        if ($depth > 1) return;
        foreach (new DirectoryIterator($absBase) as $item) {
            if (!$item->isDir() || $item->isDot()) continue;
            $name = $item->getFilename();
            if (in_array($name, $skip) || $name[0] === '.') continue;
            $abs = $item->getPathname();
            $rel = ($relBase ? $relBase . '/' : '') . $name;
            if (file_exists($abs . DIRECTORY_SEPARATOR . '.htaccess')) {
                $result[] = [
                    'rel'  => $rel,
                    'abs'  => $abs,
                    'name' => $name,
                    'user' => folderUser($abs),
                ];
            }
            $scan($abs, $rel, $depth + 1);
        }
    };
    $scan(BASE_DIR, '', 0);
    usort($result, fn($a,$b) => strcasecmp($a['rel'], $b['rel']));
    return $result;
}

// ════════════════════════════════════════════════════════════
//  SETUP (primera ejecución)
// ════════════════════════════════════════════════════════════
$needsSetup = !file_exists(CONFIG_FILE);
$setupError = null;

if ($needsSetup) {
    if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['setup'])) {
        $su  = trim($_POST['su'] ?? '');
        $sp  = $_POST['sp']  ?? '';
        $sp2 = $_POST['sp2'] ?? '';
        if (!$su)                   $setupError = 'El usuario no puede estar vacío.';
        elseif (strlen($sp) < 6)   $setupError = 'La contraseña debe tener al menos 6 caracteres.';
        elseif ($sp !== $sp2)      $setupError = 'Las contraseñas no coinciden.';
        if (!$setupError) {
            $hash = password_hash($sp, PASSWORD_BCRYPT, ['cost' => 12]);
            $php  = "<?php\nreturn ['user'=>" . var_export($su, true) . ",'hash'=>" . var_export($hash, true) . "];\n";
            file_put_contents(CONFIG_FILE, $php);
            header('Location: index.php'); exit;
        }
    }
    ?><!DOCTYPE html>
<html lang="es"><head>
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>Configuración inicial</title>
<style>
*{box-sizing:border-box;margin:0;padding:0}
body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;font-size:13px;background:#f1f5f9;display:flex;align-items:center;justify-content:center;min-height:100vh}
.card{background:#fff;padding:32px;border-radius:12px;box-shadow:0 10px 25px rgba(0,0,0,.1);width:360px}
h2{margin-bottom:6px;font-size:18px;font-weight:700}p{color:#64748b;font-size:12px;margin-bottom:20px}
.field{margin-bottom:12px}label{display:block;font-size:11px;font-weight:700;margin-bottom:4px;color:#374151;text-transform:uppercase;letter-spacing:.04em}
input{width:100%;padding:8px 11px;border:1px solid #e2e8f0;border-radius:6px;font-size:13px;outline:none}
input:focus{border-color:#3b82f6;box-shadow:0 0 0 3px rgba(59,130,246,.12)}
.btn{display:block;width:100%;padding:10px;background:#3b82f6;color:#fff;border:none;border-radius:6px;font-size:13px;font-weight:700;cursor:pointer;margin-top:8px}
.btn:hover{background:#2563eb}.error{color:#dc2626;font-size:12px;margin-top:10px;text-align:center;background:#fef2f2;border:1px solid #fecaca;border-radius:6px;padding:7px}
</style>
</head><body>
<div class="card">
  <h2>Configuración inicial</h2>
  <p>Creá el usuario administrador para comenzar.</p>
  <form method="post">
    <input type="hidden" name="setup" value="1">
    <div class="field"><label>Usuario</label><input type="text" name="su" autofocus required value="<?= h($_POST['su']??'') ?>"></div>
    <div class="field"><label>Contraseña</label><input type="password" name="sp" required></div>
    <div class="field"><label>Repetir contraseña</label><input type="password" name="sp2" required></div>
    <button class="btn" type="submit">Crear administrador</button>
    <?php if ($setupError): ?><p class="error"><?= h($setupError) ?></p><?php endif; ?>
  </form>
</div>
</body></html><?php
    exit;
}

// ════════════════════════════════════════════════════════════
//  CARGAR CONFIG
// ════════════════════════════════════════════════════════════
$cfg = include CONFIG_FILE;
define('ADMIN_USER', $cfg['user']);
define('ADMIN_HASH', $cfg['hash'] ?? '');

// ════════════════════════════════════════════════════════════
//  ROUTING
// ════════════════════════════════════════════════════════════
$action = $_REQUEST['action'] ?? '';
$page   = $_GET['page'] ?? '';
$relDir = trim(str_replace('\\', '/', $_GET['dir'] ?? ''), '/');
$absDir = $relDir ? safeResolve($relDir) : BASE_DIR;
if (!$absDir || !is_dir($absDir)) { $absDir = BASE_DIR; $relDir = ''; }
$back   = $relDir ? '?dir=' . eu($relDir) : '';

// — LOGIN —
if ($action === 'login' && $_SERVER['REQUEST_METHOD'] === 'POST') {
    csrf();
    $u = trim($_POST['user'] ?? '');
    $p = $_POST['pass'] ?? '';
    if ($u === ADMIN_USER && ADMIN_HASH && password_verify($p, ADMIN_HASH)) {
        session_regenerate_id(true);
        $_SESSION['fm_admin'] = true;
        header('Location: index.php' . ($page ? '?page=' . eu($page) : $back)); exit;
    }
    $loginError = 'Usuario o contraseña incorrectos.';
}

// — LOGOUT —
if ($action === 'logout') { session_destroy(); header('Location: index.php'); exit; }

// — DOWNLOAD —
if ($action === 'download') {
    $file = basename($_GET['file'] ?? '');
    $abs  = safeResolve($relDir . '/' . $file);
    if (!$abs || !is_file($abs)) { http_response_code(404); die('Archivo no encontrado.'); }
    if (isPrivate($absDir) && !isAdmin()) { http_response_code(403); die('Acceso denegado.'); }
    header('Content-Type: ' . (mime_content_type($abs) ?: 'application/octet-stream'));
    header('Content-Disposition: attachment; filename="' . rawurlencode($file) . '"');
    header('Content-Length: ' . filesize($abs));
    readfile($abs); exit;
}

// — UPLOAD —
if ($action === 'upload' && $_SERVER['REQUEST_METHOD'] === 'POST') {
    requireAdmin(); checkCsrf();
    foreach ($_FILES['files']['tmp_name'] ?? [] as $i => $tmp) {
        if ($_FILES['files']['error'][$i] === UPLOAD_ERR_OK) {
            $name = preg_replace('/[^\w.\-\ ]/', '_', $_FILES['files']['name'][$i]);
            $dest = $absDir . DIRECTORY_SEPARATOR . $name;
            move_uploaded_file($tmp, $dest);
        }
    }
    header('Location: index.php' . $back); exit;
}

// — MKDIR —
if ($action === 'mkdir' && $_SERVER['REQUEST_METHOD'] === 'POST') {
    requireAdmin(); checkCsrf();
    $name = preg_replace('/[^\w.\-\ ]/', '_', trim($_POST['name'] ?? ''));
    if ($name) {
        $np = $absDir . DIRECTORY_SEPARATOR . $name;
        if (!file_exists($np)) {
            mkdir($np, 0755, true);
            if (!empty($_POST['private']))
                file_put_contents($np . '/.htaccess', "Options -Indexes\n");
        }
    }
    header('Location: index.php' . $back); exit;
}

// — DELETE —
if ($action === 'delete' && $_SERVER['REQUEST_METHOD'] === 'POST') {
    requireAdmin(); checkCsrf();
    $abs = safeResolve($relDir . '/' . basename($_POST['file'] ?? ''));
    if ($abs && $abs !== BASE_DIR) {
        is_dir($abs) ? rrmdir($abs) : unlink($abs);
    }
    header('Location: index.php' . $back); exit;
}

// — RENAME —
if ($action === 'rename' && $_SERVER['REQUEST_METHOD'] === 'POST') {
    requireAdmin(); checkCsrf();
    $absOld = safeResolve($relDir . '/' . basename($_POST['file'] ?? ''));
    $new    = preg_replace('/[^\w.\-\ ]/', '_', trim($_POST['newname'] ?? ''));
    if ($absOld && $new) rename($absOld, dirname($absOld) . DIRECTORY_SEPARATOR . $new);
    header('Location: index.php' . $back); exit;
}

// — TOGGLE PRIVADO —
if ($action === 'toggleprivate' && $_SERVER['REQUEST_METHOD'] === 'POST') {
    requireAdmin(); checkCsrf();
    $abs = safeResolve($relDir . '/' . basename($_POST['file'] ?? ''));
    if ($abs && is_dir($abs)) {
        $ht = $abs . DIRECTORY_SEPARATOR . '.htaccess';
        file_exists($ht) ? unlink($ht) : file_put_contents($ht, "Options -Indexes\n");
    }
    header('Location: index.php' . $back); exit;
}

// — PASSWD ADD / CHANGE —
if ($action === 'passwd_add' && $_SERVER['REQUEST_METHOD'] === 'POST') {
    requireAdmin(); checkCsrf();
    $pu  = trim($_POST['pu'] ?? '');
    $pp  = $_POST['pp']  ?? '';
    $pp2 = $_POST['pp2'] ?? '';
    $perr = ''; $pok = '';
    if (!preg_match('/^[\w.\-]{2,32}$/', $pu)) $perr = 'Usuario inválido (solo letras, números, guión bajo, punto o guión, 2-32 chars).';
    elseif (strlen($pp) < 8)                   $perr = 'La contraseña debe tener al menos 8 caracteres.';
    elseif ($pp !== $pp2)                      $perr = 'Las contraseñas no coinciden.';
    if (!$perr) {
        $existed = isset(htRead()[$pu]);
        htSetUser($pu, $pp);
        $pok = $existed
            ? "Contraseña de \"$pu\" actualizada."
            : "Usuario \"$pu\" creado.";
    }
    $qs = $perr ? '&err=' . eu($perr) : '&ok=' . eu($pok);
    header('Location: index.php?page=users' . $qs); exit;
}

// — PASSWD DEL —
if ($action === 'passwd_del' && $_SERVER['REQUEST_METHOD'] === 'POST') {
    requireAdmin(); checkCsrf();
    $pu = trim($_POST['pu'] ?? '');
    $qs = '';
    if ($pu && isset(htRead()[$pu])) {
        htDelUser($pu);
        $qs = '&ok=' . eu("Usuario \"$pu\" eliminado.");
    }
    header('Location: index.php?page=users' . $qs); exit;
}

// — ASIGNAR CARPETA A USUARIO —
if ($action === 'passwd_folder' && $_SERVER['REQUEST_METHOD'] === 'POST') {
    requireAdmin(); checkCsrf();
    $pf  = trim($_POST['pf'] ?? '');
    $pu  = trim($_POST['pu'] ?? '');
    $abs = $pf ? safeResolve($pf) : null;
    $err = ''; $ok = '';
    if (!$abs || !is_dir($abs)) {
        $err = "Carpeta no encontrada: " . $pf;
    } else {
        $wrote = $pu ? folderSetUser($abs, $pu) : folderRemoveUser($abs);
        if (!$wrote) {
            $err = "No se pudo escribir .htaccess en '$pf'. "
                 . "Verificá que el directorio sea escribible por el usuario de PHP.";
        } else {
            $ok = $pu
                ? "Carpeta \"$pf\" protegida con usuario \"$pu\"."
                : "Carpeta \"$pf\" sin contraseña (pública).";
        }
    }
    $qs = $err ? '&err=' . eu($err) : '&ok=' . eu($ok);
    header('Location: index.php?page=users' . $qs); exit;
}

// ════════════════════════════════════════════════════════════
//  PREPARAR DATOS DE VISTA
// ════════════════════════════════════════════════════════════
$accessDenied  = ($absDir !== BASE_DIR) && isPrivate($absDir) && !isAdmin();
$isRoot        = ($absDir === BASE_DIR);
$entries = $publicEntries = $privateEntries = [];

if (!$accessDenied && $page !== 'users') {
    foreach (scandir($absDir) ?: [] as $entry) {
        if (in_array($entry, $HIDDEN) || $entry[0] === '.') continue;
        $abs    = $absDir . DIRECTORY_SEPARATOR . $entry;
        $isDir  = is_dir($abs);
        $isPriv = $isDir && isPrivate($abs);
        if ($isPriv && !isAdmin() && $isRoot) continue;
        $row = [
            'name'   => $entry,
            'isDir'  => $isDir,
            'isPriv' => $isPriv,
            'size'   => $isDir ? null : filesize($abs),
            'mtime'  => filemtime($abs),
        ];
        $entries[] = $row;
        if ($isRoot) { $isPriv ? $privateEntries[] = $row : $publicEntries[] = $row; }
    }
    $cmp = fn($a,$b) => $b['isDir'] <=> $a['isDir'] ?: strcasecmp($a['name'], $b['name']);
    usort($entries, $cmp);
    usort($publicEntries, $cmp);
    usort($privateEntries, $cmp);
}

// Breadcrumb
$breadcrumb = [['label' => 'Inicio', 'path' => '', 'icon' => true]];
if ($relDir) {
    $acc = '';
    foreach (explode('/', $relDir) as $bp) {
        $acc .= ($acc ? '/' : '') . $bp;
        $breadcrumb[] = ['label' => $bp, 'path' => $acc, 'icon' => false];
    }
}
?>
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title><?= h(APP_TITLE) ?></title>
<style>
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
:root {
  --bg:       #f1f5f9;
  --card:     #ffffff;
  --border:   #e2e8f0;
  --navy:     #0f172a;
  --navy2:    #1e293b;
  --primary:  #3b82f6;
  --primary-d:#2563eb;
  --danger:   #ef4444;
  --danger-d: #dc2626;
  --success:  #10b981;
  --warning:  #f59e0b;
  --text:     #1e293b;
  --text2:    #475569;
  --muted:    #94a3b8;
  --pub-c:    #10b981;
  --priv-c:   #f59e0b;
  --r:  8px;
  --r2: 12px;
  --sh-sm: 0 1px 2px rgba(0,0,0,.05);
  --sh:    0 1px 3px rgba(0,0,0,.08), 0 1px 2px rgba(0,0,0,.05);
  --sh-md: 0 4px 6px -1px rgba(0,0,0,.08), 0 2px 4px rgba(0,0,0,.05);
  --sh-lg: 0 10px 25px -5px rgba(0,0,0,.1), 0 4px 10px rgba(0,0,0,.05);
}
body {
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
  font-size: 13px;
  background: var(--bg);
  color: var(--text);
  line-height: 1.5;
}

/* ════ TOPBAR ════ */
.topbar {
  background: var(--navy);
  border-bottom: 1px solid rgba(255,255,255,.05);
  padding: 0 24px;
  height: 54px;
  display: flex;
  align-items: center;
  gap: 16px;
  position: sticky;
  top: 0;
  z-index: 100;
}
.topbar-logo {
  display: flex;
  align-items: center;
  gap: 9px;
  text-decoration: none;
  flex-shrink: 0;
}
.topbar-logo-icon {
  width: 30px; height: 30px;
  background: var(--primary);
  border-radius: 7px;
  display: flex; align-items: center; justify-content: center;
  box-shadow: 0 2px 6px rgba(59,130,246,.5);
}
.topbar-logo-text {
  font-size: 14px;
  font-weight: 700;
  color: #fff;
  letter-spacing: -.01em;
}
.topbar-divider { width: 1px; height: 22px; background: rgba(255,255,255,.12); flex-shrink: 0; }
.breadcrumb {
  display: flex; align-items: center; gap: 4px;
  font-size: 12px; flex: 1; min-width: 0; flex-wrap: wrap;
}
.breadcrumb a {
  color: #94a3b8; text-decoration: none;
  padding: 2px 5px; border-radius: 4px;
  display: flex; align-items: center; gap: 4px;
  transition: color .15s, background .15s;
}
.breadcrumb a:hover { color: #fff; background: rgba(255,255,255,.08); }
.breadcrumb .sep { color: #334155; font-size: 14px; }
.breadcrumb .current { color: #e2e8f0; font-weight: 500; padding: 2px 5px; }
.topbar-right { display: flex; align-items: center; gap: 8px; flex-shrink: 0; }
.admin-chip {
  display: flex; align-items: center; gap: 6px;
  background: rgba(255,255,255,.07);
  border: 1px solid rgba(255,255,255,.1);
  border-radius: 99px;
  padding: 3px 12px 3px 4px;
  color: #cbd5e1; font-size: 11px; font-weight: 500;
}
.admin-chip-avatar {
  width: 22px; height: 22px;
  background: var(--primary);
  border-radius: 99px;
  display: flex; align-items: center; justify-content: center;
  font-size: 11px; color: #fff; font-weight: 700;
}

/* ════ BOTONES ════ */
.btn {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 7px 14px;
  border: none; border-radius: var(--r);
  cursor: pointer; font-size: 12px; font-weight: 600;
  text-decoration: none; white-space: nowrap; line-height: 1.4;
  transition: all .15s;
}
.btn-primary { background: var(--primary); color: #fff; box-shadow: 0 1px 2px rgba(37,99,235,.25); }
.btn-primary:hover { background: var(--primary-d); box-shadow: 0 2px 6px rgba(37,99,235,.35); }
.btn-danger  { background: var(--danger); color: #fff; box-shadow: 0 1px 2px rgba(220,38,38,.2); }
.btn-danger:hover  { background: var(--danger-d); }
.btn-outline { background: #fff; color: var(--text); border: 1px solid var(--border); }
.btn-outline:hover { background: #f8fafc; border-color: #cbd5e1; }
.btn-ghost { background: transparent; color: rgba(255,255,255,.75); border: 1px solid rgba(255,255,255,.18); }
.btn-ghost:hover { background: rgba(255,255,255,.1); color: #fff; border-color: rgba(255,255,255,.35); }
.btn-sm { padding: 4px 10px; font-size: 11px; }
.btn-icon {
  padding: 5px 7px; line-height: 1;
  background: transparent; border: 1px solid var(--border); border-radius: 6px;
  cursor: pointer; color: var(--text2); transition: all .15s;
  display: inline-flex; align-items: center; justify-content: center;
}
.btn-icon:hover { background: #f1f5f9; border-color: #cbd5e1; color: var(--text); }
.btn-icon.danger:hover { background: #fef2f2; border-color: #fecaca; color: var(--danger); }

/* ════ LAYOUT ════ */
.container { max-width: 1140px; margin: 0 auto; padding: 24px 20px; }

/* ════ TOOLBAR (admin) ════ */
.toolbar {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--r2);
  padding: 12px 16px;
  margin-bottom: 22px;
  box-shadow: var(--sh-sm);
  display: flex; align-items: flex-start; gap: 10px; flex-wrap: wrap;
}
.toolbar-sep { width: 1px; background: var(--border); align-self: stretch; margin: 0 4px; }
.form-panel {
  background: #f8fafc;
  border: 1px solid var(--border);
  border-radius: var(--r);
  padding: 14px 16px;
  margin-top: 10px;
  display: none;
}
.form-panel.open { display: block; }
.form-row { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.form-row input[type=text], .form-row input[type=password] {
  padding: 7px 11px;
  border: 1px solid var(--border);
  border-radius: var(--r);
  font-size: 13px;
  flex: 1; min-width: 150px;
  outline: none; transition: border-color .15s, box-shadow .15s;
}
.form-row input:focus { border-color: var(--primary); box-shadow: 0 0 0 3px rgba(59,130,246,.12); }
.form-row label { font-size: 12px; cursor: pointer; display: flex; align-items: center; gap: 5px; color: var(--text2); }
.upload-zone {
  border: 2px dashed var(--border);
  border-radius: var(--r);
  padding: 24px 18px;
  text-align: center;
  color: var(--muted);
  cursor: pointer;
  transition: all .2s;
}
.upload-zone:hover { border-color: var(--primary); color: var(--primary); background: rgba(59,130,246,.03); }
.upload-zone-icon { margin-bottom: 8px; }

/* ════ PAGE HEADER ════ */
.page-header {
  display: flex; align-items: center; gap: 12px;
  margin-bottom: 24px;
}
.page-header-icon {
  width: 40px; height: 40px; flex-shrink: 0;
  background: var(--navy);
  border-radius: var(--r);
  display: flex; align-items: center; justify-content: center;
}
.page-title { font-size: 18px; font-weight: 700; color: var(--text); letter-spacing: -.02em; }
.page-sub { font-size: 12px; color: var(--muted); margin-top: 2px; }

/* ════ SECTION HEADERS ════ */
.section-header {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 30px 0 14px;
}
.section-header:first-child { margin-top: 0; }
.section-label {
  display: flex; align-items: center; gap: 7px;
  font-size: 10px; font-weight: 700;
  text-transform: uppercase; letter-spacing: .09em;
  color: var(--text2); white-space: nowrap;
}
.section-dot {
  width: 7px; height: 7px;
  border-radius: 99px;
  background: var(--pub-c);
}
.section-dot.priv { background: var(--priv-c); }
.section-rule { flex: 1; height: 1px; background: var(--border); }
.section-count {
  font-size: 10px; font-weight: 600; color: var(--muted);
  background: var(--bg); border: 1px solid var(--border);
  border-radius: 99px; padding: 1px 8px;
}

/* ════ BOXES ════ */
.boxes { display: flex; flex-wrap: wrap; gap: 10px; padding-bottom: 6px; }
.box {
  width: 140px; height: 104px;
  border-radius: var(--r2);
  background: var(--card);
  border: 1px solid var(--border);
  box-shadow: var(--sh-sm);
  cursor: pointer;
  transition: box-shadow .2s, transform .2s, border-color .2s;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 7px; position: relative; overflow: hidden; user-select: none;
}
.box::after {
  content: '';
  position: absolute; top: 0; left: 0; right: 0; height: 3px;
  background: var(--pub-c);
}
.box.priv::after { background: var(--priv-c); }
.box:hover { box-shadow: var(--sh-md); transform: translateY(-3px); border-color: #cbd5e1; }
.box-icon  { line-height: 1; display: flex; align-items: center; justify-content: center; }
.box-label {
  font-size: 11px; font-weight: 600;
  text-align: center; padding: 0 10px;
  max-height: 2.8em; overflow: hidden;
  color: var(--text); line-height: 1.35;
  word-break: break-word;
}
.box-badge {
  position: absolute; top: 7px; right: 7px;
  display: flex; align-items: center;
}
.box-actions {
  position: absolute; bottom: 0; left: 0; right: 0;
  background: rgba(15,23,42,.85);
  padding: 6px 8px;
  display: none; justify-content: center; gap: 5px;
}
.box:hover .box-actions { display: flex; }
.box-actions form { display: inline; }
.box-actions button {
  background: rgba(255,255,255,.92);
  border: none; border-radius: 5px;
  padding: 4px 8px; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  transition: background .12s;
}
.box-actions button:hover { background: #fff; }
.box-actions button.danger:hover { background: #fef2f2; }

/* ════ FILE TABLE ════ */
.file-table {
  width: 100%; border-collapse: collapse;
  background: var(--card);
  border-radius: var(--r2);
  overflow: hidden;
  box-shadow: var(--sh);
  border: 1px solid var(--border);
}
.file-table th {
  background: #f8fafc;
  padding: 10px 16px;
  text-align: left;
  font-size: 10px; font-weight: 700;
  text-transform: uppercase; letter-spacing: .08em;
  color: var(--muted);
  border-bottom: 1px solid var(--border);
}
.file-table td {
  padding: 10px 16px;
  border-bottom: 1px solid #f1f5f9;
  vertical-align: middle;
}
.file-table tr:last-child td { border-bottom: none; }
.file-table tbody tr:hover td { background: #f8fafc; }
.file-name a {
  color: var(--text); text-decoration: none; font-weight: 500;
  display: flex; align-items: center; gap: 9px;
  transition: color .12s;
}
.file-name a:hover { color: var(--primary); }
.badge { border-radius: 99px; font-size: 10px; padding: 2px 9px; font-weight: 600;
         display: inline-flex; align-items: center; gap: 4px; }
.badge-priv { background: #fffbeb; color: #b45309; border: 1px solid #fde68a; }
.badge-pub  { background: #f0fdf4; color: #15803d; border: 1px solid #bbf7d0; }
.muted { color: var(--muted); font-size: 11px; }
.actions-cell { display: flex; gap: 4px; justify-content: flex-end; }

/* ── RENAME INLINE ── */
.rename-form { display: none; align-items: center; gap: 5px; margin-top: 4px; }
.rename-form.open { display: flex; }
.rename-form input {
  padding: 4px 8px;
  border: 1px solid var(--border); border-radius: 5px;
  font-size: 12px; outline: none;
}
.rename-form input:focus { border-color: var(--primary); box-shadow: 0 0 0 2px rgba(59,130,246,.12); }

/* ════ LOGIN / ACCESS DENIED ════ */
.login-wrap {
  min-height: 100vh;
  display: flex; align-items: center; justify-content: center;
  padding: 24px;
  background: var(--bg);
}
.login-card {
  background: var(--card);
  border: 1px solid var(--border);
  padding: 40px 36px;
  border-radius: var(--r2);
  box-shadow: var(--sh-lg);
  width: 380px; max-width: 100%;
}
.login-header {
  text-align: center;
  margin-bottom: 30px;
}
.login-icon-wrap {
  width: 52px; height: 52px;
  background: var(--navy);
  border-radius: var(--r2);
  display: flex; align-items: center; justify-content: center;
  margin: 0 auto 14px;
  box-shadow: var(--sh-md);
}
.login-header h2 { font-size: 20px; font-weight: 700; letter-spacing: -.02em; color: var(--text); }
.login-header p  { font-size: 12px; color: var(--muted); margin-top: 4px; }
.login-field { margin-bottom: 14px; }
.login-field label {
  display: block; font-size: 11px; font-weight: 700;
  margin-bottom: 5px; color: var(--text2); letter-spacing: .02em;
  text-transform: uppercase;
}
.login-field input {
  width: 100%; padding: 9px 12px;
  border: 1px solid var(--border); border-radius: var(--r);
  font-size: 13px; outline: none;
  transition: border-color .15s, box-shadow .15s;
}
.login-field input:focus { border-color: var(--primary); box-shadow: 0 0 0 3px rgba(59,130,246,.12); }
.login-card .btn { width: 100%; justify-content: center; padding: 10px; font-size: 13px; margin-top: 6px; }
.error-msg {
  color: var(--danger); font-size: 12px; margin-top: 10px;
  text-align: center; background: #fef2f2;
  border: 1px solid #fecaca; border-radius: 6px; padding: 7px;
}
.login-back { text-align: center; margin-top: 16px; }
.login-back a { font-size: 12px; color: var(--muted); text-decoration: none; }
.login-back a:hover { color: var(--text2); }

/* ════ USERS PAGE ════ */
.users-layout {
  display: grid;
  grid-template-columns: 340px 1fr;
  gap: 20px;
  align-items: start;
}
@media (max-width: 860px) { .users-layout { grid-template-columns: 1fr; } }
.panel {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--r2);
  box-shadow: var(--sh);
  overflow: hidden;
}
.panel-header {
  padding: 13px 18px;
  border-bottom: 1px solid var(--border);
  display: flex; align-items: center; gap: 10px;
  background: #f8fafc;
}
.panel-title { font-size: 13px; font-weight: 700; color: var(--text); flex: 1; }
.panel-body { padding: 18px; }
.user-row {
  display: flex; align-items: center; gap: 10px;
  padding: 10px 0;
  border-bottom: 1px solid #f1f5f9;
}
.user-row:last-child { border-bottom: none; }
.user-avatar {
  width: 34px; height: 34px; flex-shrink: 0;
  background: var(--navy2);
  border-radius: var(--r);
  display: flex; align-items: center; justify-content: center;
  font-size: 13px; font-weight: 700; color: #fff;
  text-transform: uppercase;
}
.user-info { flex: 1; min-width: 0; }
.user-name { font-size: 13px; font-weight: 600; color: var(--text); }
.user-folders { font-size: 11px; color: var(--muted); margin-top: 1px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.folder-row {
  display: flex; align-items: center; gap: 10px;
  padding: 9px 0;
  border-bottom: 1px solid #f1f5f9;
  flex-wrap: wrap;
}
.folder-row:last-child { border-bottom: none; }
.folder-name {
  flex: 1; min-width: 140px;
  font-size: 12px; font-weight: 500; color: var(--text);
  display: flex; align-items: center; gap: 7px;
}
.folder-path { font-size: 10px; color: var(--muted); font-weight: 400; }
select.user-select {
  padding: 5px 8px;
  border: 1px solid var(--border);
  border-radius: var(--r);
  font-size: 12px;
  background: #fff;
  outline: none;
  cursor: pointer;
  min-width: 130px;
  transition: border-color .15s;
}
select.user-select:focus { border-color: var(--primary); box-shadow: 0 0 0 2px rgba(59,130,246,.12); }
.field-group { display: flex; flex-direction: column; gap: 12px; }
.field-item {}
.field-item label {
  display: block; font-size: 11px; font-weight: 700;
  text-transform: uppercase; letter-spacing: .04em;
  color: var(--text2); margin-bottom: 5px;
}
.field-item input {
  width: 100%; padding: 8px 11px;
  border: 1px solid var(--border); border-radius: var(--r);
  font-size: 13px; outline: none;
  transition: border-color .15s, box-shadow .15s;
}
.field-item input:focus { border-color: var(--primary); box-shadow: 0 0 0 3px rgba(59,130,246,.12); }
.alert-box {
  display: flex; align-items: flex-start; gap: 8px;
  background: #fef2f2; border: 1px solid #fecaca; border-radius: var(--r);
  padding: 10px 12px; color: var(--danger); font-size: 12px;
  margin-bottom: 16px;
}
.success-box {
  display: flex; align-items: flex-start; gap: 8px;
  background: #f0fdf4; border: 1px solid #bbf7d0; border-radius: var(--r);
  padding: 10px 12px; color: #15803d; font-size: 12px;
  margin-bottom: 16px;
}
.empty-panel { text-align: center; padding: 30px 20px; color: var(--muted); }
.empty-panel p { font-size: 12px; margin-top: 8px; }

/* ════ MISC ════ */
.empty { text-align: center; padding: 60px 20px; color: var(--muted); }
.empty-icon { margin-bottom: 10px; opacity: .4; display: flex; justify-content: center; }
.empty p { font-size: 13px; }
@media (max-width: 640px) {
  .topbar { padding: 0 14px; gap: 10px; }
  .container { padding: 16px 12px; }
  .hide-sm { display: none; }
}
</style>
</head>
<body>

<?php
// ════════════════════════════════════════════════════════════
//  LOGIN PAGE
// ════════════════════════════════════════════════════════════
if ($action === 'login' && !isAdmin()): ?>
<div class="login-wrap">
  <div class="login-card">
    <div class="login-header">
      <div class="login-icon-wrap"><?= icon('database', 26, '#fff') ?></div>
      <h2><?= h(APP_TITLE) ?></h2>
      <p>Ingresá tus credenciales para continuar</p>
    </div>
    <form method="post" action="index.php<?= $page ? '?page=' . eu($page) : $back ?>">
      <input type="hidden" name="action" value="login">
      <input type="hidden" name="_csrf" value="<?= h(csrf()) ?>">
      <div class="login-field"><label>Usuario</label><input type="text" name="user" autofocus autocomplete="username"></div>
      <div class="login-field"><label>Contraseña</label><input type="password" name="pass" autocomplete="current-password"></div>
      <button class="btn btn-primary" type="submit"><?= icon('log-out', 14, '#fff') ?> Iniciar sesión</button>
      <?php if (!empty($loginError)): ?><p class="error-msg"><?= h($loginError) ?></p><?php endif; ?>
    </form>
  </div>
</div>
<?php exit_body(); ?>
<?php endif; ?>

<?php
// ════════════════════════════════════════════════════════════
//  ACCESS DENIED
// ════════════════════════════════════════════════════════════
if ($accessDenied): ?>
<div class="login-wrap">
  <div class="login-card">
    <div class="login-header">
      <div class="login-icon-wrap"><?= icon('lock', 24, '#fff') ?></div>
      <h2>Área restringida</h2>
      <p>Esta carpeta requiere autenticación de administrador</p>
    </div>
    <form method="post" action="index.php<?= $back ?>">
      <input type="hidden" name="action" value="login">
      <input type="hidden" name="_csrf" value="<?= h(csrf()) ?>">
      <div class="login-field"><label>Usuario</label><input type="text" name="user" autofocus autocomplete="username"></div>
      <div class="login-field"><label>Contraseña</label><input type="password" name="pass" autocomplete="current-password"></div>
      <button class="btn btn-primary" type="submit"><?= icon('log-out', 14, '#fff') ?> Iniciar sesión</button>
      <?php if (!empty($loginError)): ?><p class="error-msg"><?= h($loginError) ?></p><?php endif; ?>
    </form>
    <div class="login-back"><a href="index.php">← Volver al inicio</a></div>
  </div>
</div>
<?php exit_body(); ?>
<?php endif; ?>

<?php
// ════════════════════════════════════════════════════════════
//  TOPBAR
// ════════════════════════════════════════════════════════════
?>
<div class="topbar">
  <a href="index.php" class="topbar-logo">
    <div class="topbar-logo-icon"><?= icon('database', 16, '#fff') ?></div>
    <span class="topbar-logo-text"><?= h(APP_TITLE) ?></span>
  </a>
  <?php if (count($breadcrumb) > 1 || $page): ?>
  <div class="topbar-divider"></div>
  <nav class="breadcrumb">
    <?php foreach ($breadcrumb as $i => $crumb): ?>
      <?php if ($i > 0): ?><span class="sep">›</span><?php endif; ?>
      <?php if ($i < count($breadcrumb) - 1): ?>
        <a href="index.php<?= $crumb['path'] ? '?dir=' . eu($crumb['path']) : '' ?>">
          <?php if (!empty($crumb['icon'])): ?><?= icon('home', 12, '#94a3b8') ?><?php endif; ?>
          <?= h($crumb['label']) ?>
        </a>
      <?php elseif (!$page): ?>
        <span class="current">
          <?php if (!empty($crumb['icon'])): ?><?= icon('home', 12, '#e2e8f0') ?><?php endif; ?>
          <?= h($crumb['label']) ?>
        </span>
      <?php else: ?>
        <a href="index.php<?= $crumb['path'] ? '?dir=' . eu($crumb['path']) : '' ?>">
          <?php if (!empty($crumb['icon'])): ?><?= icon('home', 12, '#94a3b8') ?><?php endif; ?>
          <?= h($crumb['label']) ?>
        </a>
      <?php endif; ?>
    <?php endforeach; ?>
    <?php if ($page === 'users'): ?>
      <span class="sep">›</span>
      <span class="current"><?= icon('users', 12, '#e2e8f0') ?> Usuarios</span>
    <?php endif; ?>
  </nav>
  <?php endif; ?>
  <div class="topbar-right">
    <?php if (isAdmin()): ?>
      <div class="admin-chip">
        <div class="admin-chip-avatar"><?= strtoupper(substr(ADMIN_USER, 0, 1)) ?></div>
        <?= h(ADMIN_USER) ?>
      </div>
      <a href="index.php?action=logout" class="btn btn-ghost btn-sm"><?= icon('log-out', 13, 'currentColor') ?> Salir</a>
    <?php else: ?>
      <a href="index.php?action=login" class="btn btn-ghost btn-sm"><?= icon('lock', 13, 'currentColor') ?> Iniciar sesión</a>
    <?php endif; ?>
  </div>
</div>

<div class="container">

<?php
// ════════════════════════════════════════════════════════════
//  ADMIN ACTION BAR
// ════════════════════════════════════════════════════════════
if (isAdmin()): ?>
<div class="toolbar">

  <!-- Subir archivos -->
  <div>
    <button class="btn btn-primary" onclick="toggle('form-upload')"><?= icon('upload', 14, '#fff') ?> Subir archivos</button>
    <div id="form-upload" class="form-panel">
      <form method="post" action="index.php<?= $back ?>" enctype="multipart/form-data">
        <input type="hidden" name="action" value="upload">
        <input type="hidden" name="_csrf" value="<?= h(csrf()) ?>">
        <div class="upload-zone" onclick="document.getElementById('fileinput').click()">
          <div class="upload-zone-icon"><?= icon('upload', 28, '#94a3b8') ?></div>
          <div>Hacé clic o arrastrá archivos aquí</div>
          <div style="font-size:11px;margin-top:4px;color:#94a3b8">Máximo <?= fmtSize(MAX_UPLOAD) ?> por archivo</div>
        </div>
        <input type="file" id="fileinput" name="files[]" multiple style="display:none" onchange="updateFileList(this)">
        <div id="file-list" style="margin:8px 0;font-size:12px;color:var(--muted)"></div>
        <button class="btn btn-primary" type="submit" style="margin-top:6px"><?= icon('upload', 13, '#fff') ?> Subir</button>
        <button class="btn btn-outline" type="button" onclick="toggle('form-upload')" style="margin-top:6px">Cancelar</button>
      </form>
    </div>
  </div>

  <div class="toolbar-sep"></div>

  <!-- Nueva carpeta -->
  <div>
    <button class="btn btn-outline" onclick="toggle('form-mkdir')"><?= icon('folder-plus', 14) ?> Nueva carpeta</button>
    <div id="form-mkdir" class="form-panel">
      <form method="post" action="index.php<?= $back ?>">
        <input type="hidden" name="action" value="mkdir">
        <input type="hidden" name="_csrf" value="<?= h(csrf()) ?>">
        <div class="form-row">
          <input type="text" name="name" placeholder="Nombre de la carpeta" autofocus>
          <label><input type="checkbox" name="private" value="1"> <?= icon('lock', 12) ?> Privada</label>
          <button class="btn btn-primary" type="submit"><?= icon('check', 13, '#fff') ?> Crear</button>
          <button class="btn btn-outline" type="button" onclick="toggle('form-mkdir')"><?= icon('x', 12) ?> Cancelar</button>
        </div>
      </form>
    </div>
  </div>

  <?php if ($isRoot || $page === 'users'): ?>
  <div class="toolbar-sep"></div>
  <a href="index.php?page=users" class="btn <?= $page === 'users' ? 'btn-primary' : 'btn-outline' ?>"><?= icon('users', 14, $page === 'users' ? '#fff' : 'currentColor') ?> Usuarios</a>
  <?php endif; ?>

</div>
<?php endif; ?>

<?php
// ════════════════════════════════════════════════════════════
//  PÁGINA: GESTIÓN DE USUARIOS / HTPASSWD
// ════════════════════════════════════════════════════════════
if ($page === 'users'):
    if (!isAdmin()) {
        echo '<div class="login-wrap"><div class="login-card"><div class="login-header">';
        echo '<div class="login-icon-wrap">' . icon('lock', 24, '#fff') . '</div>';
        echo '<h2>Área restringida</h2><p>Debés iniciar sesión como administrador.</p></div>';
        echo '<div class="login-back"><a href="index.php?action=login&page=users">Iniciar sesión →</a></div></div></div>';
        exit_body();
    }
    $htUsers     = htRead();
    $userFolders = htUserFolders();
    $allDirs     = allPrivateDirs();
    $htErr       = $_GET['err'] ?? '';
    $htOk        = $_GET['ok']  ?? '';
?>

<div class="page-header">
  <div class="page-header-icon"><?= icon('users', 22, '#fff') ?></div>
  <div>
    <div class="page-title">Gestión de usuarios</div>
    <div class="page-sub">Administrá credenciales de acceso y permisos por carpeta</div>
  </div>
</div>

<?php if ($htErr): ?>
<div class="alert-box"><?= icon('alert', 15, 'var(--danger)') ?> <?= h($htErr) ?></div>
<?php endif; ?>
<?php if ($htOk): ?>
<div class="success-box"><?= icon('check', 15, '#15803d') ?> <?= h($htOk) ?></div>
<?php endif; ?>

<div class="users-layout">

  <!-- Columna izquierda: Agregar usuario + Lista -->
  <div style="display:flex;flex-direction:column;gap:20px;">

    <!-- Agregar / cambiar contraseña -->
    <div class="panel">
      <div class="panel-header">
        <?= icon('user-plus', 15, 'var(--primary)') ?>
        <span class="panel-title">Agregar / actualizar usuario</span>
      </div>
      <div class="panel-body">
        <form method="post" action="index.php?page=users">
          <input type="hidden" name="action" value="passwd_add">
          <input type="hidden" name="_csrf" value="<?= h(csrf()) ?>">
          <div class="field-group">
            <div class="field-item">
              <label>Usuario</label>
              <input type="text" name="pu" placeholder="nombre_usuario" pattern="^\w{2,32}$"
                     title="Solo letras, números y guión bajo (2-32 chars)" required autocomplete="off">
            </div>
            <div class="field-item">
              <label>Contraseña</label>
              <input type="password" name="pp" placeholder="Mínimo 8 caracteres" minlength="8" required autocomplete="new-password">
            </div>
            <div class="field-item">
              <label>Repetir contraseña</label>
              <input type="password" name="pp2" placeholder="Repetir contraseña" minlength="8" required autocomplete="new-password">
            </div>
            <button class="btn btn-primary" type="submit" style="margin-top:4px"><?= icon('check', 13, '#fff') ?> Guardar usuario</button>
          </div>
        </form>
      </div>
    </div>

    <!-- Lista de usuarios -->
    <div class="panel">
      <div class="panel-header">
        <?= icon('users', 15, 'var(--text2)') ?>
        <span class="panel-title">Usuarios registrados</span>
        <span style="font-size:11px;color:var(--muted);font-weight:600"><?= count($htUsers) ?></span>
      </div>
      <div class="panel-body" style="padding:8px 18px;">
        <?php if (empty($htUsers)): ?>
          <div class="empty-panel"><?= icon('users', 28, '#cbd5e1') ?><p>Sin usuarios registrados</p></div>
        <?php else: ?>
          <?php foreach ($htUsers as $uName => $uHash): ?>
          <div class="user-row">
            <div class="user-avatar"><?= h(substr($uName, 0, 2)) ?></div>
            <div class="user-info">
              <div class="user-name"><?= h($uName) ?></div>
              <?php $uFolders = $userFolders[$uName] ?? []; ?>
              <div class="user-folders">
                <?php if ($uFolders): ?>
                  <?= icon('folder', 10, '#94a3b8') ?> <?= h(implode(', ', $uFolders)) ?>
                <?php else: ?>
                  Sin carpetas asignadas
                <?php endif; ?>
              </div>
            </div>
            <form method="post" action="index.php?page=users" style="display:inline"
                  onsubmit="return confirm('¿Eliminar usuario <?= h(addslashes($uName)) ?>?')">
              <input type="hidden" name="action" value="passwd_del">
              <input type="hidden" name="_csrf" value="<?= h(csrf()) ?>">
              <input type="hidden" name="pu" value="<?= h($uName) ?>">
              <button class="btn-icon danger" type="submit" title="Eliminar usuario"><?= icon('user-x', 14, 'var(--danger)') ?></button>
            </form>
          </div>
          <?php endforeach; ?>
        <?php endif; ?>
      </div>
    </div>

  </div><!-- /col-izquierda -->

  <!-- Columna derecha: Permisos de carpetas -->
  <div class="panel">
    <div class="panel-header">
      <?= icon('key', 15, 'var(--warning)') ?>
      <span class="panel-title">Permisos por carpeta</span>
      <span style="font-size:11px;color:var(--muted);font-weight:600"><?= count($allDirs) ?></span>
    </div>
    <div class="panel-body" style="padding:8px 18px;">
      <?php if (empty($allDirs)): ?>
        <div class="empty-panel"><?= icon('folder', 28, '#cbd5e1') ?><p>No hay carpetas privadas configuradas</p></div>
      <?php else: ?>
        <?php foreach ($allDirs as $dir): ?>
        <div class="folder-row">
          <div class="folder-name">
            <?= $dir['user'] ? icon('lock', 13, '#f59e0b') : icon('globe', 13, '#10b981') ?>
            <div>
              <?= h($dir['name']) ?>
              <?php if (str_contains($dir['rel'], '/')): ?>
                <span class="folder-path"><?= h(dirname($dir['rel'])) ?>/</span>
              <?php endif; ?>
            </div>
          </div>
          <form method="post" action="index.php?page=users" style="display:flex;align-items:center;gap:6px">
            <input type="hidden" name="action" value="passwd_folder">
            <input type="hidden" name="_csrf" value="<?= h(csrf()) ?>">
            <input type="hidden" name="pf" value="<?= h($dir['rel']) ?>">
            <select name="pu" class="user-select" onchange="this.form.submit()">
              <option value="">— sin contraseña —</option>
              <?php foreach ($htUsers as $uName => $uHash): ?>
                <option value="<?= h($uName) ?>" <?= $dir['user'] === $uName ? 'selected' : '' ?>><?= h($uName) ?></option>
              <?php endforeach; ?>
            </select>
          </form>
        </div>
        <?php endforeach; ?>
      <?php endif; ?>
    </div>
  </div><!-- /panel folders -->

</div><!-- /users-layout -->

<?php
exit_body();
endif; // page === users
?>

<?php
// ════════════════════════════════════════════════════════════
//  VISTA RAÍZ — cajas pública / privada
// ════════════════════════════════════════════════════════════
if ($isRoot): ?>

  <!-- SECCIÓN PÚBLICA -->
  <div class="section-header">
    <div class="section-label"><span class="section-dot"></span>Acceso General</div>
    <div class="section-rule"></div>
    <span class="section-count"><?= count($publicEntries) ?></span>
  </div>
  <div class="boxes">
    <?php if (empty($publicEntries)): ?>
      <div class="empty"><div class="empty-icon"><?= icon('folder', 36, '#cbd5e1') ?></div><p>Sin elementos públicos</p></div>
    <?php endif; ?>
    <?php foreach ($publicEntries as $e): ?>
      <?php renderBox($e, $relDir); ?>
    <?php endforeach; ?>
  </div>

  <?php if (isAdmin()): ?>
  <!-- SECCIÓN PRIVADA (solo admin) -->
  <div class="section-header">
    <div class="section-label"><span class="section-dot priv"></span>Acceso Privado</div>
    <div class="section-rule"></div>
    <span class="section-count"><?= count($privateEntries) ?></span>
  </div>
  <div class="boxes">
    <?php if (empty($privateEntries)): ?>
      <div class="empty"><div class="empty-icon"><?= icon('lock', 36, '#cbd5e1') ?></div><p>Sin carpetas privadas</p></div>
    <?php endif; ?>
    <?php foreach ($privateEntries as $e): ?>
      <?php renderBox($e, $relDir); ?>
    <?php endforeach; ?>
  </div>
  <?php endif; ?>

<?php
// ════════════════════════════════════════════════════════════
//  VISTA SUBDIRECTORIO — tabla de archivos
// ════════════════════════════════════════════════════════════
else: ?>

  <table class="file-table">
    <thead>
      <tr>
        <th style="width:46%">Nombre</th>
        <th class="hide-sm">Tamaño</th>
        <th class="hide-sm">Modificado</th>
        <?php if (isAdmin()): ?><th style="width:160px;text-align:right">Acciones</th><?php endif; ?>
      </tr>
    </thead>
    <tbody>
      <!-- Enlace al nivel superior -->
      <?php if ($relDir): ?>
      <tr>
        <td colspan="<?= isAdmin() ? 4 : 3 ?>">
          <a href="index.php<?= count($breadcrumb) > 2 ? '?dir=' . eu(implode('/', array_slice(explode('/', $relDir), 0, -1))) : '' ?>"
             style="color:var(--muted);text-decoration:none;font-size:12px;display:inline-flex;align-items:center;gap:5px">
            <?= icon('chevron-r', 13, 'var(--muted)') ?> Volver
          </a>
        </td>
      </tr>
      <?php endif; ?>

      <?php if (empty($entries)): ?>
      <tr><td colspan="<?= isAdmin() ? 4 : 3 ?>">
        <div class="empty">
          <div class="empty-icon"><?= icon('folder', 36, '#cbd5e1') ?></div>
          <p>Carpeta vacía</p>
        </div>
      </td></tr>
      <?php endif; ?>

      <?php foreach ($entries as $e):
        $eHref = 'index.php?' . ($e['isDir'] ? 'dir=' . eu(($relDir ? $relDir . '/' : '') . $e['name'])
                                               : 'action=download&dir=' . eu($relDir) . '&file=' . eu($e['name']));
        $iconColor = $e['isDir'] ? ($e['isPriv'] ? '#b45309' : '#15803d') : '#64748b';
      ?>
      <tr>
        <!-- Nombre -->
        <td class="file-name">
          <a href="<?= h($eHref) ?>">
            <?= fileIcon($e['name'], $e['isDir'], 18, $iconColor) ?>
            <?= h(str_replace('alphaconsultaoperaciones', 'alphaconsultaoper..', $e['name'])) ?>
          </a>
          <?php if ($e['isDir']): ?>
            <span class="badge <?= $e['isPriv'] ? 'badge-priv' : 'badge-pub' ?>">
              <?= $e['isPriv'] ? icon('lock', 10, '#b45309') : icon('globe', 10, '#15803d') ?>
              <?= $e['isPriv'] ? 'privada' : 'pública' ?>
            </span>
          <?php endif; ?>
        </td>
        <!-- Tamaño -->
        <td class="muted hide-sm"><?= fmtSize($e['size']) ?></td>
        <!-- Fecha -->
        <td class="muted hide-sm"><?= fmtDate($e['mtime']) ?></td>
        <!-- Acciones admin -->
        <?php if (isAdmin()): ?>
        <td>
          <div class="actions-cell">
            <!-- Descargar (solo archivos) -->
            <?php if (!$e['isDir']): ?>
            <a href="<?= h('index.php?action=download&dir=' . eu($relDir) . '&file=' . eu($e['name'])) ?>"
               class="btn-icon" title="Descargar"><?= icon('download', 14) ?></a>
            <?php endif; ?>
            <!-- Toggle privado (solo carpetas) -->
            <?php if ($e['isDir']): ?>
            <form method="post" action="index.php<?= $back ?>" style="display:inline"
                  onsubmit="return confirm('¿Cambiar visibilidad de <?= h(addslashes($e['name'])) ?>?')">
              <input type="hidden" name="action" value="toggleprivate">
              <input type="hidden" name="_csrf" value="<?= h(csrf()) ?>">
              <input type="hidden" name="file" value="<?= h($e['name']) ?>">
              <button class="btn-icon" type="submit" title="<?= $e['isPriv'] ? 'Hacer pública' : 'Hacer privada' ?>">
                <?= $e['isPriv'] ? icon('unlock', 14) : icon('lock', 14) ?>
              </button>
            </form>
            <?php endif; ?>
            <!-- Renombrar -->
            <button class="btn-icon" title="Renombrar" onclick="toggleRename('<?= h(md5($e['name'])) ?>')"><?= icon('edit', 14) ?></button>
            <!-- Eliminar -->
            <form method="post" action="index.php<?= $back ?>" style="display:inline"
                  onsubmit="return confirm('¿Eliminar <?= h(addslashes($e['name'])) ?>? Esta acción no se puede deshacer.')">
              <input type="hidden" name="action" value="delete">
              <input type="hidden" name="_csrf" value="<?= h(csrf()) ?>">
              <input type="hidden" name="file" value="<?= h($e['name']) ?>">
              <button class="btn-icon danger" type="submit" title="Eliminar"><?= icon('trash', 14) ?></button>
            </form>
          </div>
          <!-- Formulario rename inline -->
          <div class="rename-form" id="rename-<?= h(md5($e['name'])) ?>">
            <form method="post" action="index.php<?= $back ?>" style="display:flex;gap:4px;align-items:center">
              <input type="hidden" name="action" value="rename">
              <input type="hidden" name="_csrf" value="<?= h(csrf()) ?>">
              <input type="hidden" name="file" value="<?= h($e['name']) ?>">
              <input type="text" name="newname" value="<?= h($e['name']) ?>" style="width:130px">
              <button class="btn btn-primary btn-sm" type="submit"><?= icon('check', 12, '#fff') ?></button>
              <button class="btn btn-outline btn-sm" type="button" onclick="toggleRename('<?= h(md5($e['name'])) ?>')"><?= icon('x', 12) ?></button>
            </form>
          </div>
        </td>
        <?php endif; ?>
      </tr>
      <?php endforeach; ?>
    </tbody>
  </table>

<?php endif; ?>
</div><!-- /container -->

<script>
function toggle(id) {
  var el = document.getElementById(id);
  el.classList.toggle('open');
}
function toggleRename(hash) {
  var el = document.getElementById('rename-' + hash);
  if (el) el.classList.toggle('open');
}
function updateFileList(input) {
  var list = document.getElementById('file-list');
  var files = Array.from(input.files).map(function(f){
    return '· ' + f.name + ' (' + fmtBytes(f.size) + ')';
  });
  list.innerHTML = files.length ? files.join('<br>') : '';
}
function fmtBytes(b) {
  var units = ['B','KB','MB','GB'];
  for (var i = 0; i < units.length; i++) {
    if (b < 1024) return (Math.round(b * 10) / 10) + ' ' + units[i];
    b /= 1024;
  }
  return (Math.round(b * 100) / 100) + ' TB';
}
function initBoxes() {
  document.querySelectorAll('.box[data-href]').forEach(function(box) {
    box.addEventListener('click', function(e) {
      if (e.target.closest('.box-actions')) return;
      window.location.href = box.dataset.href;
    });
  });
}
document.addEventListener('DOMContentLoaded', initBoxes);
</script>
<?php
// ════════════════════════════════════════════════════════════
//  HELPERS DE RENDERIZADO
// ════════════════════════════════════════════════════════════
function renderBox(array $e, string $relDir): void {
    if ($e['isDir']) {
        $href = 'index.php?dir=' . eu(($relDir ? $relDir . '/' : '') . $e['name']);
    } else {
        $href = 'index.php?action=download&dir=' . eu($relDir) . '&file=' . eu($e['name']);
    }
    $label      = str_replace('alphaconsultaoperaciones', 'alphaconsultaoper..', $e['name']);
    $cls        = $e['isPriv'] ? 'priv' : 'pub';
    $csrf       = csrf();
    $back       = $relDir ? '?dir=' . eu($relDir) : '';
    $iconColor  = $e['isDir'] ? ($e['isPriv'] ? '#f59e0b' : '#10b981') : '#64748b';

    echo '<div class="box ' . $cls . '" data-href="' . h($href) . '">';
    echo '<span class="box-icon">' . fileIcon($e['name'], $e['isDir'], 32, $iconColor) . '</span>';
    echo '<span class="box-label">' . h(strtolower($label)) . '</span>';
    if ($e['isPriv']) echo '<span class="box-badge">' . icon('lock', 11, '#f59e0b') . '</span>';
    if (isAdmin()) {
        echo '<div class="box-actions">';
        if ($e['isDir']) {
            echo '<form method="post" action="index.php' . $back . '">';
            echo '<input type="hidden" name="action" value="toggleprivate">';
            echo '<input type="hidden" name="_csrf" value="' . h($csrf) . '">';
            echo '<input type="hidden" name="file" value="' . h($e['name']) . '">';
            echo '<button type="submit" title="' . ($e['isPriv'] ? 'Hacer pública' : 'Hacer privada') . '">'
                . ($e['isPriv'] ? icon('unlock', 14) : icon('lock', 14)) . '</button>';
            echo '</form>';
        }
        $confirmMsg = 'Eliminar ' . addslashes($e['name']) . '? Esta acción no se puede deshacer.';
        echo '<form method="post" action="index.php' . $back . '"'
            . ' onsubmit="return confirm(\'' . $confirmMsg . '\')">';
        echo '<input type="hidden" name="action" value="delete">';
        echo '<input type="hidden" name="_csrf" value="' . h($csrf) . '">';
        echo '<input type="hidden" name="file" value="' . h($e['name']) . '">';
        echo '<button type="submit" class="danger" title="Eliminar">' . icon('trash', 14, '#dc2626') . '</button>';
        echo '</form>';
        echo '</div>';
    }
    echo '</div>';
}

function exit_body(): void {
    echo "\n</body>\n</html>";
    exit;
}
?>
</body>
</html>
