db = Database::getInstance(); } // ───────────────────────────────────────────────────────────────────────── // Registro de actividad // ───────────────────────────────────────────────────────────────────────── /** * Registra una acción en el log. * * @param int|null $adminId admin_users.id * @param string $modulo pacientes|ordenes|domicilios|enfermeras|asignaciones * @param string $accion crear|editar|eliminar|autorizar|rechazar|asignar... * @param int|null $entidadId ID del registro afectado * @param mixed $detalle Texto o array (se serializa como JSON) */ public function registrar( ?int $adminId, string $modulo, string $accion, ?int $entidadId = null, $detalle = null ): int { $adminNombre = null; if ($adminId) { $row = $this->db->fetch( 'SELECT full_name FROM admin_users WHERE id = ?', [$adminId] ); $adminNombre = $row['full_name'] ?? null; } $detalleStr = is_array($detalle) || is_object($detalle) ? json_encode($detalle, JSON_UNESCAPED_UNICODE) : (string)($detalle ?? ''); // Resolver IP real del cliente (detrás de proxy/Coolify/Nginx) $ip = null; foreach (['HTTP_X_FORWARDED_FOR','HTTP_X_REAL_IP','HTTP_CF_CONNECTING_IP','REMOTE_ADDR'] as $h) { if (!empty($_SERVER[$h])) { $ip = trim(explode(',', $_SERVER[$h])[0]); break; } } return $this->db->insert('lab_actividad_admin', [ 'admin_id' => $adminId, 'admin_nombre'=> $adminNombre, 'modulo' => $modulo, 'accion' => $accion, 'entidad_id' => $entidadId, 'detalle' => $detalleStr, 'ip_address' => $ip, 'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? null, ]); } // ───────────────────────────────────────────────────────────────────────── // Consultas // ───────────────────────────────────────────────────────────────────────── /** * Actividad reciente del módulo. * * @param int $limit * @param string $modulo Filtro opcional por módulo */ public function reciente(int $limit = 50, string $modulo = ''): array { $where = $modulo ? 'WHERE a.modulo = ?' : ''; $params = $modulo ? [$modulo] : []; $params[] = $limit; return $this->db->fetchAll(" SELECT a.*, u.username AS admin_username FROM lab_actividad_admin a LEFT JOIN admin_users u ON u.id = a.admin_id $where ORDER BY a.created_at DESC LIMIT ? ", $params); } /** * Actividad de un administrador específico. */ public function porAdmin(int $adminId, int $limit = 100): array { return $this->db->fetchAll(" SELECT * FROM lab_actividad_admin WHERE admin_id = ? ORDER BY created_at DESC LIMIT ? ", [$adminId, $limit]); } /** * Actividad sobre una entidad concreta. */ public function porEntidad(string $modulo, int $entidadId): array { return $this->db->fetchAll(" SELECT a.*, u.username AS admin_username FROM lab_actividad_admin a LEFT JOIN admin_users u ON u.id = a.admin_id WHERE a.modulo = ? AND a.entidad_id = ? ORDER BY a.created_at ASC ", [$modulo, $entidadId]); } /** * Estadísticas de actividad por módulo en un rango de fechas. */ public function estadisticas(string $desde, string $hasta): array { return $this->db->fetchAll(" SELECT modulo, accion, COUNT(*) AS total FROM lab_actividad_admin WHERE DATE(created_at) BETWEEN ? AND ? GROUP BY modulo, accion ORDER BY modulo, total DESC ", [$desde, $hasta]); } /** * Filtrado avanzado con paginación. * * @param array $opts {desde, hasta, modulo, accion, admin_id, buscar, page, per_page} * @return array {data, total, paginas, pagina} */ public function filtrar(array $opts = []): array { $where = []; $params = []; if (!empty($opts['desde'])) { $where[] = 'DATE(a.created_at) >= ?'; $params[] = $opts['desde']; } if (!empty($opts['hasta'])) { $where[] = 'DATE(a.created_at) <= ?'; $params[] = $opts['hasta']; } if (!empty($opts['modulo'])) { $where[] = 'a.modulo = ?'; $params[] = $opts['modulo']; } if (!empty($opts['accion'])) { $where[] = 'a.accion = ?'; $params[] = $opts['accion']; } if (!empty($opts['admin_id'])) { $where[] = 'a.admin_id = ?'; $params[] = (int)$opts['admin_id']; } if (!empty($opts['buscar'])) { $where[] = '(a.admin_nombre LIKE ? OR a.detalle LIKE ?)'; $like = '%' . $opts['buscar'] . '%'; $params[] = $like; $params[] = $like; } $whereStr = $where ? 'WHERE ' . implode(' AND ', $where) : ''; $total = (int)($this->db->fetch( "SELECT COUNT(*) AS n FROM lab_actividad_admin a $whereStr", $params )['n'] ?? 0); $pp = max(1, (int)($opts['per_page'] ?? 50)); $pag = max(1, (int)($opts['page'] ?? 1)); $off = ($pag - 1) * $pp; $pParams = $params; $pParams[] = $pp; $pParams[] = $off; $data = $this->db->fetchAll(" SELECT a.*, u.username AS admin_username FROM lab_actividad_admin a LEFT JOIN admin_users u ON u.id = a.admin_id $whereStr ORDER BY a.created_at DESC LIMIT ? OFFSET ? ", $pParams); return [ 'data' => $data, 'total' => $total, 'paginas' => (int)ceil($total / $pp), 'pagina' => $pag, ]; } }