diff --git a/public/admin/api.php b/public/admin/api.php new file mode 100644 index 0000000..2331e23 --- /dev/null +++ b/public/admin/api.php @@ -0,0 +1,132 @@ + true, 'data' => $data, 'user' => Auth::user()], JSON_UNESCAPED_UNICODE); + exit; +} +function err(string $msg, int $code = 400): void { + http_response_code($code); + echo json_encode(['ok' => false, 'error' => $msg], JSON_UNESCAPED_UNICODE); + exit; +} +function body(): array { + return json_decode((string) file_get_contents('php://input'), true) ?? []; +} + +$method = $_SERVER['REQUEST_METHOD']; +$action = $_GET['action'] ?? ''; +$me = Auth::userId(); +$pdo = Db::pdo(); + +try { + if ($method === 'GET' && $action === 'list_users') { + ok(Lookups::allUsers()); + } + + if ($method === 'POST' && $action === 'create_user') { + $b = body(); + $email = strtolower(trim((string)($b['email'] ?? ''))); + if ($email === '') err('email requerido'); + if (!filter_var($email, FILTER_VALIDATE_EMAIL)) err('email inválido'); + + $name = trim((string)($b['name'] ?? '')); + $role = in_array(($b['role'] ?? ''), ROLES, true) ? $b['role'] : 'usuario'; + $apellido = trim((string)($b['apellido'] ?? '')); + $activo = (int)($b['activo'] ?? 1) === 1 ? 1 : 0; + $orden = (int)($b['orden'] ?? 0); + + $exists = $pdo->prepare('SELECT id FROM users WHERE email = :e'); + $exists->execute([':e' => $email]); + if ($exists->fetch()) err('Ya existe un usuario con ese email', 409); + + $ins = $pdo->prepare( + 'INSERT INTO users (email, name, role, apellido, activo, orden, first_seen_at, last_login_at) + VALUES (:email, :name, :role, :apellido, :activo, :orden, NULL, NULL)' + ); + $ins->execute([ + ':email' => $email, ':name' => $name, ':role' => $role, + ':apellido' => $apellido, ':activo' => $activo, ':orden' => $orden, + ]); + $id = (int)$pdo->lastInsertId(); + + $sel = $pdo->prepare('SELECT id, email, name, role, apellido, activo, orden, first_seen_at, last_login_at FROM users WHERE id = :id'); + $sel->execute([':id' => $id]); + $row = $sel->fetch(); + $row['id'] = (int)$row['id']; + $row['activo'] = (int)$row['activo']; + $row['orden'] = (int)$row['orden']; + ok($row); + } + + if ($method === 'PUT' && $action === 'update_user') { + $b = body(); + $id = (int)($b['_id'] ?? 0); + if (!$id) err('id requerido'); + + $sel = $pdo->prepare('SELECT id, email, role, activo FROM users WHERE id = :id'); + $sel->execute([':id' => $id]); + $existing = $sel->fetch(); + if (!$existing) err('Usuario no encontrado', 404); + + $newRole = in_array(($b['role'] ?? $existing['role']), ROLES, true) ? $b['role'] : $existing['role']; + $newActivo = isset($b['activo']) ? ((int)$b['activo'] === 1 ? 1 : 0) : (int)$existing['activo']; + + // Salvaguardas: el socio no puede demoverse ni desactivarse a sí mismo + if ((int)$existing['id'] === $me) { + if ($newRole !== 'socio') err('No puedes quitarte el rol de Socio a ti mismo', 400); + if ($newActivo === 0) err('No puedes desactivarte a ti mismo', 400); + } + + $newEmail = isset($b['email']) ? strtolower(trim((string)$b['email'])) : $existing['email']; + if (!filter_var($newEmail, FILTER_VALIDATE_EMAIL)) err('email inválido'); + if ($newEmail !== $existing['email']) { + $chk = $pdo->prepare('SELECT id FROM users WHERE email = :e AND id != :id'); + $chk->execute([':e' => $newEmail, ':id' => $id]); + if ($chk->fetch()) err('Otro usuario ya tiene ese email', 409); + } + + $name = trim((string)($b['name'] ?? '')); + $apellido = trim((string)($b['apellido'] ?? '')); + $orden = (int)($b['orden'] ?? 0); + + $upd = $pdo->prepare( + 'UPDATE users SET email = :email, name = :name, role = :role, + apellido = :apellido, activo = :activo, orden = :orden + WHERE id = :id' + ); + $upd->execute([ + ':email' => $newEmail, ':name' => $name, ':role' => $newRole, + ':apellido' => $apellido, ':activo' => $newActivo, ':orden' => $orden, + ':id' => $id, + ]); + + $sel = $pdo->prepare('SELECT id, email, name, role, apellido, activo, orden, first_seen_at, last_login_at FROM users WHERE id = :id'); + $sel->execute([':id' => $id]); + $row = $sel->fetch(); + $row['id'] = (int)$row['id']; + $row['activo'] = (int)$row['activo']; + $row['orden'] = (int)$row['orden']; + ok($row); + } + + err('Acción no reconocida', 404); + +} catch (PDOException $e) { + error_log('admin db error: ' . $e->getMessage()); + err('Error de base de datos', 500); +} catch (Throwable $e) { + error_log('admin error: ' . $e->getMessage()); + err('Error interno', 500); +} diff --git a/public/admin/index.php b/public/admin/index.php new file mode 100644 index 0000000..98393f9 --- /dev/null +++ b/public/admin/index.php @@ -0,0 +1,8 @@ +prepare('SELECT id, texto, abogado, autor_email, fecha FROM tramites_obs WHERE tramite_id = :t ORDER BY fecha ASC'); $obs->execute([':t' => $tid]); $row['obs'] = $obs->fetchAll(); @@ -89,17 +90,79 @@ function hydrate(PDO $pdo, array $row): array { return $h; }, $hrs->fetchAll()); + $inv = $pdo->prepare( + 'SELECT u.id, u.email, u.name, u.apellido + FROM tramites_involucrados ti + JOIN users u ON u.id = ti.user_id + WHERE ti.tramite_id = :t + ORDER BY u.name ASC, u.email ASC' + ); + $inv->execute([':t' => $tid]); + $row['involucrados'] = array_map(function ($u) { + $u['id'] = (int)$u['id']; + return $u; + }, $inv->fetchAll()); + return $row; } +/** + * ¿El usuario actual puede ver/editar este trámite? + * Socio → siempre + * Resto → si es creador, o abogado solicitante (apellido), o involucrado + */ +function canView(PDO $pdo, int $tid): bool { + if (Auth::isSocio()) return true; + $email = Auth::user()['email']; + $uid = Auth::userId(); + $stmt = $pdo->prepare( + "SELECT 1 FROM tramites_legales t + LEFT JOIN users ua ON ua.apellido = t.abogado AND ua.activo = 1 + LEFT JOIN tramites_involucrados ti ON ti.tramite_id = t.id AND ti.user_id = :uid + WHERE t.id = :tid + AND (t.creado_por = :email OR ua.id = :uid2 OR ti.user_id IS NOT NULL) + LIMIT 1" + ); + $stmt->execute([':tid' => $tid, ':email' => $email, ':uid' => $uid, ':uid2' => $uid]); + return $stmt->fetchColumn() !== false; +} + +/** Reemplaza la lista de involucrados del trámite por la lista dada (array de user_id). */ +function syncInvolucrados(PDO $pdo, int $tid, array $userIds, string $addedBy): void { + $pdo->prepare('DELETE FROM tramites_involucrados WHERE tramite_id = :t')->execute([':t' => $tid]); + if (empty($userIds)) return; + $placeholders = implode(',', array_fill(0, count($userIds), '?')); + $check = $pdo->prepare("SELECT id FROM users WHERE activo = 1 AND id IN ($placeholders)"); + $check->execute(array_map('intval', $userIds)); + $valid = array_map('intval', $check->fetchAll(PDO::FETCH_COLUMN)); + if (!$valid) return; + $ins = $pdo->prepare('INSERT INTO tramites_involucrados (tramite_id, user_id, added_by) VALUES (?,?,?)'); + foreach ($valid as $uid) $ins->execute([$tid, $uid, $addedBy]); +} + $method = $_SERVER['REQUEST_METHOD']; $action = $_GET['action'] ?? ''; $user = Auth::user(); +$myId = Auth::userId(); $pdo = Db::pdo(); try { if ($method === 'GET' && $action === 'list') { - $rows = $pdo->query('SELECT * FROM tramites_legales ORDER BY id DESC')->fetchAll(); + if (Auth::isSocio()) { + $rows = $pdo->query('SELECT * FROM tramites_legales ORDER BY id DESC')->fetchAll(); + } else { + $stmt = $pdo->prepare( + "SELECT DISTINCT t.* FROM tramites_legales t + LEFT JOIN users ua ON ua.apellido = t.abogado AND ua.activo = 1 + LEFT JOIN tramites_involucrados ti ON ti.tramite_id = t.id AND ti.user_id = :uid + WHERE t.creado_por = :email + OR ua.id = :uid2 + OR ti.user_id IS NOT NULL + ORDER BY t.id DESC" + ); + $stmt->execute([':email' => $user['email'], ':uid' => $myId, ':uid2' => $myId]); + $rows = $stmt->fetchAll(); + } $grouped = ['adm' => [], 'corp' => [], 'mig' => [], 'lit' => []]; foreach ($rows as $r) { $grouped[$r['departamento']][] = hydrate($pdo, $r); @@ -108,6 +171,8 @@ try { } if ($method === 'POST' && $action === 'create') { + if (!Auth::canCreate()) err('Tu rol no permite crear trámites. Solo Socios y Abogados pueden.', 403); + $b = body(); $dept = dept_of($b); @@ -156,6 +221,12 @@ try { ':user' => $user['email'], ]); $id = (int) $pdo->lastInsertId(); + + // Involucrados: si vienen del payload, se respetan; si no, se auto-incluye al creador + $involIds = array_map('intval', (array)($b['involucrados'] ?? [])); + if ($myId > 0 && !in_array($myId, $involIds, true)) $involIds[] = $myId; + syncInvolucrados($pdo, $id, $involIds, $user['email']); + $row = $pdo->prepare('SELECT * FROM tramites_legales WHERE id = :id'); $row->execute([':id' => $id]); ok(hydrate($pdo, $row->fetch())); @@ -166,6 +237,8 @@ try { $id = (int) ($b['_id'] ?? 0); if (!$id) err('id requerido'); + if (!canView($pdo, $id)) err('No tienes acceso a este trámite', 403); + $cur = $pdo->prepare('SELECT * FROM tramites_legales WHERE id = :id'); $cur->execute([':id' => $id]); $before = $cur->fetch(); @@ -212,6 +285,11 @@ try { ':user' => $user['email'], ':id' => $id, ]); + + if (array_key_exists('involucrados', $b) && is_array($b['involucrados'])) { + syncInvolucrados($pdo, $id, array_map('intval', $b['involucrados']), $user['email']); + } + $row = $pdo->prepare('SELECT * FROM tramites_legales WHERE id = :id'); $row->execute([':id' => $id]); ok(hydrate($pdo, $row->fetch())); @@ -223,6 +301,8 @@ try { $val = (string) ($b['estatus'] ?? ''); if (!$id) err('id requerido'); + if (!canView($pdo, $id)) err('No tienes acceso a este trámite', 403); + $cur = $pdo->prepare('SELECT departamento FROM tramites_legales WHERE id = :id'); $cur->execute([':id' => $id]); $dept = $cur->fetchColumn(); @@ -242,6 +322,8 @@ try { if (!$id) err('id requerido'); if ($texto === '') err('texto requerido'); + if (!canView($pdo, $id)) err('No tienes acceso a este trámite', 403); + $pdo->prepare('INSERT INTO tramites_obs (tramite_id, texto, abogado, autor_email) VALUES (?,?,?,?)') ->execute([$id, $texto, $abog, $user['email']]); @@ -262,6 +344,8 @@ try { if (!$id) err('id requerido'); if ($hrs <= 0) err('horas debe ser > 0'); + if (!canView($pdo, $id)) err('No tienes acceso a este trámite', 403); + $pdo->prepare('INSERT INTO tramites_hours (tramite_id, abogado, hrs, descripcion, autor_email) VALUES (?,?,?,?,?)') ->execute([$id, $abog, $hrs, $desc, $user['email']]); @@ -274,8 +358,13 @@ try { } if ($method === 'DELETE' && $action === 'delete') { + if (!Auth::canDelete()) err('Tu rol no permite eliminar trámites. Solo Socios y Abogados pueden.', 403); + $id = (int) ($_GET['id'] ?? 0); if (!$id) err('id requerido'); + + if (!canView($pdo, $id)) err('No tienes acceso a este trámite', 403); + $pdo->prepare('DELETE FROM tramites_legales WHERE id = :id')->execute([':id' => $id]); ok(['deleted' => $id]); } diff --git a/public/tramites/index.php b/public/tramites/index.php index c26659e..c211628 100644 --- a/public/tramites/index.php +++ b/public/tramites/index.php @@ -1,8 +1,8 @@ prepare( + 'SELECT id, email, name, role, apellido, activo + FROM users WHERE email = :e LIMIT 1' + ); + $stmt->execute([':e' => $email]); + $row = $stmt->fetch(); + self::$userRowCache = $row ?: self::defaultRow(); + } catch (Throwable $e) { + error_log('Auth::userRow fetch failed: ' . $e->getMessage()); + self::$userRowCache = self::defaultRow(); + } + return self::$userRowCache; + } + + private static function defaultRow(): array { + return ['id' => 0, 'email' => '', 'name' => '', 'role' => 'usuario', 'apellido' => '', 'activo' => 1]; + } + + public static function userId(): int { return (int)(self::userRow()['id'] ?? 0); } + public static function role(): string { return (string)(self::userRow()['role'] ?? 'usuario'); } + public static function apellido(): string { return (string)(self::userRow()['apellido'] ?? ''); } + + public static function isSocio(): bool { return self::role() === 'socio'; } + public static function isAbogado(): bool { return in_array(self::role(), ['socio','abogado'], true); } + public static function canCreate(): bool { return self::isAbogado(); } // socio + abogado pueden crear trámites + public static function canDelete(): bool { return self::isAbogado(); } // socio + abogado pueden eliminar + + public static function requireSocio(): void { + self::requireAuth(); + if (!self::isSocio()) { + $accepts = $_SERVER['HTTP_ACCEPT'] ?? ''; + $isAjax = !empty($_SERVER['HTTP_X_REQUESTED_WITH']) + || str_contains($accepts, 'application/json'); + http_response_code(403); + if ($isAjax) { + header('Content-Type: application/json; charset=utf-8'); + echo json_encode(['ok' => false, 'error' => 'Acceso restringido a Socios']); + } else { + header('Content-Type: text/html; charset=utf-8'); + echo '
El Panel de Control es exclusivo para Socios. Si crees que deberías tener acceso, contacta al administrador.
' + . '← Volver al portalAdministra la lista unificada de usuarios del sistema. El campo Apellido es lo que aparece en el dropdown "Abogado Solicitante" (solo si el rol es Socio o Abogado). Los emails que empiezan con placeholder- son los apellidos sembrados al inicio para que los dropdowns no salieran vacíos; reemplázalos con los emails reales conforme los abogados se logueen.
| Nombre | +Apellido | +Rol | +Activo | +Orden | +Último login | ++ |
|---|