Overview
Audit Logs provide a comprehensive, searchable record of all administrative actions and system events, ensuring transparency, accountability, and compliance.All admin actions are automatically logged with timestamps, user IDs, IP addresses, and detailed descriptions.
Audit Logs Screen
Access the audit log viewer from the admin panel:admin/presentation/screens/audit_logs_screen.dart
class AuditLogsScreen extends StatefulWidget {
final int adminId;
const AuditLogsScreen({
super.key,
required this.adminId,
});
}
Log Entry Structure
class AuditLog {
final int id;
final int? adminId;
final int? usuarioAfectadoId;
final String accion;
final String? descripcion;
final String? ipAddress;
final String? userAgent;
final DateTime fechaCreacion;
final Map<String, dynamic>? metadata;
}
Database Schema
CREATE TABLE audit_logs (
id INT AUTO_INCREMENT PRIMARY KEY,
admin_id INT,
usuario_afectado_id INT,
accion VARCHAR(100) NOT NULL,
descripcion TEXT,
ip_address VARCHAR(45),
user_agent VARCHAR(255),
metadata JSON,
fecha_creacion TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (admin_id) REFERENCES usuarios(id),
FOREIGN KEY (usuario_afectado_id) REFERENCES usuarios(id),
INDEX idx_admin_id (admin_id),
INDEX idx_accion (accion),
INDEX idx_fecha_creacion (fecha_creacion)
);
Logged Actions
- Authentication
- User Management
- Driver Management
- Trip Management
- Configuration
Actions:
admin_login- Admin logged inadmin_logout- Admin logged outfailed_login- Failed login attemptsession_expired- Session timeout
Actions:
create_user- New user createdupdate_user- User profile updatedactivate_user- User account activateddeactivate_user- User account deactivateddelete_user- User deleted
Actions:
approve_driver- Driver approvedreject_driver- Driver rejected (with reason)verify_documents- Driver documents verifiedsuspend_driver- Driver suspended
Actions:
cancel_trip- Trip cancelled by adminreassign_driver- Driver reassignedadjust_price- Trip price adjustedresolve_dispute- Dispute resolved
Actions:
update_pricing- Pricing configuration changedupdate_service_area- Service area modifiedupdate_company- Company information updated
Audit Log Screen Implementation
Search Functionality
admin/presentation/screens/audit_logs_screen.dart
Widget _buildSearchBar() {
return Container(
decoration: BoxDecoration(
color: const Color(0xFF1A1A1A).withValues(alpha: 0.8),
borderRadius: BorderRadius.circular(20),
border: Border.all(color: Colors.white.withValues(alpha: 0.1)),
),
child: TextField(
controller: _searchController,
style: TextStyle(color: Colors.white),
decoration: InputDecoration(
hintText: 'Buscar logs...',
prefixIcon: Icon(Icons.search_rounded),
suffixIcon: _searchController.text.isNotEmpty
? IconButton(
icon: Icon(Icons.clear),
onPressed: () {
_searchController.clear();
_loadLogs();
},
)
: null,
border: InputBorder.none,
),
onSubmitted: (_) => _loadLogs(),
),
);
}
Filter Chips
admin/presentation/screens/audit_logs_screen.dart
Widget _buildFilterChips() {
return SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: [
_buildFilterChip('Todos', null, Icons.all_inclusive_rounded),
_buildFilterChip('Login', 'login', Icons.login_rounded),
_buildFilterChip('Crear', 'crear', Icons.add_circle_outline_rounded),
_buildFilterChip('Actualizar', 'actualizar', Icons.edit_rounded),
_buildFilterChip('Eliminar', 'eliminar', Icons.delete_outline_rounded),
],
),
);
}
Widget _buildFilterChip(String label, String? value, IconData icon) {
final isSelected = _selectedFilter == value;
return GestureDetector(
onTap: () {
setState(() => _selectedFilter = value);
_loadLogs();
},
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
decoration: BoxDecoration(
color: isSelected
? const Color(0xFF667eea).withValues(alpha: 0.3)
: const Color(0xFF1A1A1A).withValues(alpha: 0.8),
borderRadius: BorderRadius.circular(16),
border: Border.all(
color: isSelected
? const Color(0xFF667eea)
: Colors.white.withValues(alpha: 0.1),
),
),
child: Row(
children: [
Icon(icon, size: 18),
const SizedBox(width: 6),
Text(label),
],
),
),
);
}
Log Card Display
admin/presentation/screens/audit_logs_screen.dart
Widget _buildLogCard(Map<String, dynamic> log) {
final accion = log['accion'] ?? '';
final descripcion = log['descripcion'] ?? '';
final usuario = '${log['nombre'] ?? ''} ${log['apellido'] ?? ''}'.trim();
final email = log['email'] ?? '';
final fecha = _formatDate(log['fecha_creacion']);
final actionColor = _getActionColor(accion);
final actionIcon = _getActionIcon(accion);
return ClipRRect(
borderRadius: BorderRadius.circular(20),
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10),
child: Container(
decoration: BoxDecoration(
color: const Color(0xFF1A1A1A).withValues(alpha: 0.8),
borderRadius: BorderRadius.circular(20),
border: Border.all(color: Colors.white.withValues(alpha: 0.1)),
),
child: Material(
color: Colors.transparent,
child: InkWell(
borderRadius: BorderRadius.circular(20),
onTap: () => _showLogDetails(log),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: [
Row(
children: [
Container(
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: actionColor.withValues(alpha: 0.2),
borderRadius: BorderRadius.circular(12),
),
child: Icon(actionIcon, color: actionColor, size: 20),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
accion.toUpperCase(),
style: TextStyle(
color: actionColor,
fontSize: 12,
fontWeight: FontWeight.bold,
),
),
Text(
descripcion,
style: TextStyle(fontSize: 14),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
],
),
),
],
),
if (usuario.isNotEmpty || email.isNotEmpty)
_buildUserInfo(usuario, email),
],
),
),
),
),
),
),
);
}
Action Color Coding
admin/presentation/screens/audit_logs_screen.dart
Color _getActionColor(String accion) {
final lower = accion.toLowerCase();
if (lower.contains('login') || lower.contains('acceso')) {
return const Color(0xFF11998e); // Green
}
if (lower.contains('crear') || lower.contains('registro')) {
return const Color(0xFF667eea); // Blue
}
if (lower.contains('actualizar') || lower.contains('editar')) {
return AppColors.primary; // Yellow
}
if (lower.contains('eliminar') || lower.contains('desactivar')) {
return const Color(0xFFf5576c); // Red
}
return const Color(0xFF667eea); // Default blue
}
IconData _getActionIcon(String accion) {
final lower = accion.toLowerCase();
if (lower.contains('login') || lower.contains('acceso')) {
return Icons.login_rounded;
}
if (lower.contains('crear') || lower.contains('registro')) {
return Icons.add_circle_rounded;
}
if (lower.contains('actualizar') || lower.contains('editar')) {
return Icons.edit_rounded;
}
if (lower.contains('eliminar') || lower.contains('desactivar')) {
return Icons.delete_rounded;
}
return Icons.info_rounded;
}
Log Details Modal
admin/presentation/screens/audit_logs_screen.dart
void _showLogDetails(Map<String, dynamic> log) {
showModalBottomSheet(
context: context,
backgroundColor: Colors.transparent,
isScrollControlled: true,
builder: (context) => ClipRRect(
borderRadius: const BorderRadius.vertical(top: Radius.circular(24)),
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10),
child: Container(
padding: const EdgeInsets.all(24),
decoration: BoxDecoration(
color: const Color(0xFF1A1A1A).withValues(alpha: 0.95),
borderRadius: const BorderRadius.vertical(top: Radius.circular(24)),
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
_buildDetailRow('Acción', log['accion']),
_buildDetailRow('Descripción', log['descripcion']),
_buildDetailRow('Usuario', '${log['nombre']} ${log['apellido']}'),
_buildDetailRow('Email', log['email']),
_buildDetailRow('IP', log['ip_address']),
_buildDetailRow('User Agent', log['user_agent']),
_buildDetailRow('Fecha', _formatFullDate(log['fecha_creacion'])),
],
),
),
),
),
);
}
Widget _buildDetailRow(String label, String? value) {
if (value == null || value.isEmpty) return const SizedBox.shrink();
return Padding(
padding: const EdgeInsets.only(bottom: 16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(label, style: TextStyle(fontSize: 12, color: Colors.white60)),
const SizedBox(height: 6),
Container(
width: double.infinity,
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.white.withValues(alpha: 0.05),
borderRadius: BorderRadius.circular(10),
),
child: Text(value, style: TextStyle(fontSize: 14)),
),
],
),
);
}
Time Formatting
admin/presentation/screens/audit_logs_screen.dart
String _formatDate(String? dateStr) {
if (dateStr == null) return '';
try {
final date = DateTime.parse(dateStr);
final now = DateTime.now();
final diff = now.difference(date);
if (diff.inMinutes < 1) return 'Ahora';
if (diff.inMinutes < 60) return 'Hace ${diff.inMinutes}m';
if (diff.inHours < 24) return 'Hace ${diff.inHours}h';
if (diff.inDays < 7) return 'Hace ${diff.inDays}d';
return DateFormat('dd/MM/yy').format(date);
} catch (e) {
return dateStr;
}
}
String _formatFullDate(String? dateStr) {
if (dateStr == null) return '';
try {
final date = DateTime.parse(dateStr);
return DateFormat('dd/MM/yyyy HH:mm:ss').format(date);
} catch (e) {
return dateStr;
}
}
Creating Audit Log Entries
PHP Backend Implementation
backend/utils/audit_logger.php
function logAuditAction(
$pdo,
$adminId,
$accion,
$descripcion,
$usuarioAfectadoId = null,
$metadata = null
) {
$ipAddress = $_SERVER['REMOTE_ADDR'] ?? null;
$userAgent = $_SERVER['HTTP_USER_AGENT'] ?? null;
$stmt = $pdo->prepare("
INSERT INTO audit_logs (
admin_id,
usuario_afectado_id,
accion,
descripcion,
ip_address,
user_agent,
metadata
) VALUES (?, ?, ?, ?, ?, ?, ?)
");
$stmt->execute([
$adminId,
$usuarioAfectadoId,
$accion,
$descripcion,
$ipAddress,
$userAgent,
json_encode($metadata)
]);
}
Usage Example
// When approving a driver
logAuditAction(
$pdo,
$adminId,
'approve_driver',
"Conductor aprobado: {$conductor['nombre']} (ID: {$conductorId})",
$conductorId,
['previous_status' => 'pendiente', 'new_status' => 'aprobado']
);
// When rejecting a driver
logAuditAction(
$pdo,
$adminId,
'reject_driver',
"Conductor rechazado: {$conductor['nombre']} - Motivo: {$motivo}",
$conductorId,
['motivo' => $motivo, 'previous_status' => 'pendiente']
);
Retrieving Audit Logs
Backend API Endpoint
backend/admin/get_audit_logs.php
// GET /admin/get_audit_logs.php?admin_id=1&page=1&per_page=50&filter=login
$stmt = $pdo->prepare("
SELECT
al.*,
u.nombre,
u.apellido,
u.email
FROM audit_logs al
LEFT JOIN usuarios u ON al.usuario_afectado_id = u.id
WHERE al.admin_id = ?
AND (? IS NULL OR al.accion LIKE ?)
ORDER BY al.fecha_creacion DESC
LIMIT ? OFFSET ?
");
$filter = $_GET['filter'] ?? null;
$filterParam = $filter ? "%{$filter}%" : null;
$limit = intval($_GET['per_page'] ?? 50);
$offset = (intval($_GET['page'] ?? 1) - 1) * $limit;
$stmt->execute([$adminId, $filterParam, $filterParam, $limit, $offset]);
Export Audit Logs
Export functionality is planned for future releases. Will support CSV and PDF formats for compliance reporting.
Retention Policy
Define how long to keep audit logs:-- Delete logs older than 1 year
DELETE FROM audit_logs
WHERE fecha_creacion < DATE_SUB(NOW(), INTERVAL 1 YEAR);
-- Archive old logs to separate table
INSERT INTO audit_logs_archive
SELECT * FROM audit_logs
WHERE fecha_creacion < DATE_SUB(NOW(), INTERVAL 6 MONTH);
DELETE FROM audit_logs
WHERE fecha_creacion < DATE_SUB(NOW(), INTERVAL 6 MONTH);
Best Practices
Log All Admin Actions
Log All Admin Actions
Ensure every administrative action is logged for complete audit trail.
Include Context
Include Context
Log not just what action was taken, but why and by whom, with before/after states.
Protect Log Integrity
Protect Log Integrity
Audit logs should be write-only for admins - no deletion or modification allowed.
Review Regularly
Review Regularly
Periodically review audit logs to identify suspicious patterns or security issues.
Compliance
Compliance
Maintain logs for required retention period for regulatory compliance.
Related Features
Dashboard
View recent activity summary
User Management
User change tracking
Driver Management
Driver approval history