db = getDB(); } public function todos(bool $incluirEliminados = false): array { $sql = "SELECT u.*, r.nombre AS rol_nombre FROM usuarios u JOIN roles r ON r.id = u.rol_id WHERE u.activo = 1"; if (!$incluirEliminados) $sql .= " AND u.deleted_at IS NULL"; $sql .= " ORDER BY u.nombre, u.apellido"; return $this->db->query($sql)->fetchAll(); } public function buscarPorId(int $id): ?array { $stmt = $this->db->prepare( "SELECT u.*, r.nombre AS rol_nombre, r.permisos FROM usuarios u JOIN roles r ON r.id = u.rol_id WHERE u.id = ? AND u.deleted_at IS NULL" ); $stmt->execute([$id]); return $stmt->fetch() ?: null; } public function buscarPorUsername(string $username): ?array { $stmt = $this->db->prepare( "SELECT u.*, r.nombre AS rol_nombre, r.permisos FROM usuarios u JOIN roles r ON r.id = u.rol_id WHERE u.username = ? AND u.deleted_at IS NULL" ); $stmt->execute([$username]); return $stmt->fetch() ?: null; } public function buscarPorEmail(string $email): ?array { $stmt = $this->db->prepare( "SELECT * FROM usuarios WHERE email = ? AND deleted_at IS NULL" ); $stmt->execute([$email]); return $stmt->fetch() ?: null; } public function buscarPorToken(string $token): ?array { $stmt = $this->db->prepare( "SELECT * FROM usuarios WHERE token_recuperacion = ? AND deleted_at IS NULL" ); $stmt->execute([$token]); return $stmt->fetch() ?: null; } public function crear(array $datos): int { $stmt = $this->db->prepare( "INSERT INTO usuarios (rol_id, nombre, apellido, email, username, password_hash, cargo, area, supervisor_id) VALUES (:rol_id, :nombre, :apellido, :email, :username, :password_hash, :cargo, :area, :supervisor_id)" ); $stmt->execute([ ':rol_id' => $datos['rol_id'], ':nombre' => $datos['nombre'], ':apellido' => $datos['apellido'], ':email' => $datos['email'], ':username' => $datos['username'], ':password_hash' => password_hash($datos['password'], PASSWORD_BCRYPT, ['cost' => 12]), ':cargo' => $datos['cargo'] ?? null, ':area' => $datos['area'] ?? null, ':supervisor_id' => $datos['supervisor_id'] ?? null, ]); return (int)$this->db->lastInsertId(); } public function actualizar(int $id, array $datos): bool { $stmt = $this->db->prepare( "UPDATE usuarios SET rol_id=:rol_id, nombre=:nombre, apellido=:apellido, email=:email, username=:username, cargo=:cargo, area=:area, supervisor_id=:supervisor_id, activo=:activo WHERE id=:id" ); return $stmt->execute([ ':rol_id' => $datos['rol_id'], ':nombre' => $datos['nombre'], ':apellido' => $datos['apellido'], ':email' => $datos['email'], ':username' => $datos['username'], ':cargo' => $datos['cargo'] ?? null, ':area' => $datos['area'] ?? null, ':supervisor_id'=> $datos['supervisor_id'] ?? null, ':activo' => $datos['activo'] ?? 1, ':id' => $id, ]); } public function actualizarPassword(int $id, string $hash): bool { $stmt = $this->db->prepare( "UPDATE usuarios SET password_hash=?, token_recuperacion=NULL, token_expira=NULL WHERE id=?" ); return $stmt->execute([$hash, $id]); } public function actualizarUltimoLogin(int $id): void { $stmt = $this->db->prepare("UPDATE usuarios SET ultimo_login=NOW() WHERE id=?"); $stmt->execute([$id]); } public function guardarTokenRecuperacion(int $id, string $token, string $expira): void { $stmt = $this->db->prepare( "UPDATE usuarios SET token_recuperacion=?, token_expira=? WHERE id=?" ); $stmt->execute([$token, $expira, $id]); } public function eliminarLogico(int $id): bool { $stmt = $this->db->prepare("UPDATE usuarios SET deleted_at=NOW(), activo=0 WHERE id=?"); return $stmt->execute([$id]); } public function restaurar(int $id): bool { $stmt = $this->db->prepare("UPDATE usuarios SET deleted_at=NULL, activo=1 WHERE id=?"); return $stmt->execute([$id]); } public function emailExiste(string $email, ?int $excepto = null): bool { $sql = "SELECT id FROM usuarios WHERE email=? AND deleted_at IS NULL"; $params = [$email]; if ($excepto) { $sql .= " AND id != ?"; $params[] = $excepto; } return (bool)$this->db->prepare($sql)->execute($params) && $this->db->query("SELECT FOUND_ROWS()")->fetchColumn(); } public function roles(): array { return $this->db->query("SELECT * FROM roles ORDER BY id")->fetchAll(); } public function usuariosParaSelector(): array { return $this->db->query( "SELECT id, CONCAT(nombre,' ',apellido) AS nombre_completo, email, area FROM usuarios WHERE activo=1 AND deleted_at IS NULL ORDER BY nombre" )->fetchAll(); } }