> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/Braian551/viax/llms.txt
> Use this file to discover all available pages before exploring further.

# Audit Logs

> View system activity logs, track changes, and monitor admin actions

## Overview

Audit Logs provide a comprehensive, searchable record of all administrative actions and system events, ensuring transparency, accountability, and compliance.

<Info>
  All admin actions are automatically logged with timestamps, user IDs, IP addresses, and detailed descriptions.
</Info>

## Audit Logs Screen

Access the audit log viewer from the admin panel:

```dart admin/presentation/screens/audit_logs_screen.dart theme={null}
class AuditLogsScreen extends StatefulWidget {
  final int adminId;

  const AuditLogsScreen({
    super.key,
    required this.adminId,
  });
}
```

## Log Entry Structure

```dart theme={null}
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

```sql theme={null}
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

<Tabs>
  <Tab title="Authentication">
    **Actions:**

    * `admin_login` - Admin logged in
    * `admin_logout` - Admin logged out
    * `failed_login` - Failed login attempt
    * `session_expired` - Session timeout

    **Icon:** Login/Logout

    **Color:** Green (#11998e)
  </Tab>

  <Tab title="User Management">
    **Actions:**

    * `create_user` - New user created
    * `update_user` - User profile updated
    * `activate_user` - User account activated
    * `deactivate_user` - User account deactivated
    * `delete_user` - User deleted

    **Icon:** People

    **Color:** Blue (#667eea)
  </Tab>

  <Tab title="Driver Management">
    **Actions:**

    * `approve_driver` - Driver approved
    * `reject_driver` - Driver rejected (with reason)
    * `verify_documents` - Driver documents verified
    * `suspend_driver` - Driver suspended

    **Icon:** ID Card

    **Color:** Yellow (#FFFF00)
  </Tab>

  <Tab title="Trip Management">
    **Actions:**

    * `cancel_trip` - Trip cancelled by admin
    * `reassign_driver` - Driver reassigned
    * `adjust_price` - Trip price adjusted
    * `resolve_dispute` - Dispute resolved

    **Icon:** Route

    **Color:** Orange (#ffa726)
  </Tab>

  <Tab title="Configuration">
    **Actions:**

    * `update_pricing` - Pricing configuration changed
    * `update_service_area` - Service area modified
    * `update_company` - Company information updated

    **Icon:** Settings

    **Color:** Purple (#764ba2)
  </Tab>
</Tabs>

## Audit Log Screen Implementation

### Search Functionality

```dart admin/presentation/screens/audit_logs_screen.dart theme={null}
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

```dart admin/presentation/screens/audit_logs_screen.dart theme={null}
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

```dart admin/presentation/screens/audit_logs_screen.dart theme={null}
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

```dart admin/presentation/screens/audit_logs_screen.dart theme={null}
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

```dart admin/presentation/screens/audit_logs_screen.dart theme={null}
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

```dart admin/presentation/screens/audit_logs_screen.dart theme={null}
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

```php backend/utils/audit_logger.php theme={null}
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

```php theme={null}
// 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

```php backend/admin/get_audit_logs.php theme={null}
// 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

<Warning>
  Export functionality is planned for future releases. Will support CSV and PDF formats for compliance reporting.
</Warning>

## Retention Policy

Define how long to keep audit logs:

```sql theme={null}
-- 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

<Accordion title="Log All Admin Actions">
  Ensure every administrative action is logged for complete audit trail.
</Accordion>

<Accordion title="Include Context">
  Log not just what action was taken, but why and by whom, with before/after states.
</Accordion>

<Accordion title="Protect Log Integrity">
  Audit logs should be write-only for admins - no deletion or modification allowed.
</Accordion>

<Accordion title="Review Regularly">
  Periodically review audit logs to identify suspicious patterns or security issues.
</Accordion>

<Accordion title="Compliance">
  Maintain logs for required retention period for regulatory compliance.
</Accordion>

## Related Features

<CardGroup cols={3}>
  <Card title="Dashboard" icon="gauge" href="/admin/dashboard-analytics">
    View recent activity summary
  </Card>

  <Card title="User Management" icon="users" href="/admin/user-management">
    User change tracking
  </Card>

  <Card title="Driver Management" icon="id-card" href="/admin/driver-management">
    Driver approval history
  </Card>
</CardGroup>
