> For the complete documentation index, see [llms.txt](https://senselab.gitbook.io/senselab-docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://senselab.gitbook.io/senselab-docs/docs/archive/database_improvements.md).

# Mejoras de Base de Datos a Nivel de Aplicación

## Resumen

Implementación de mejoras significativas en la capa de aplicación **SIN modificar la estructura de la base de datos MySQL**. Estas mejoras proporcionan funcionalidad avanzada usando los campos existentes.

## 🎯 Traits Implementados

### 1. HasCustomSoftDeletes

**Ubicación**: `app/Traits/HasCustomSoftDeletes.php`

**Propósito**: Implementa soft deletes usando el campo `eliminado` existente en lugar del `deleted_at` de Laravel.

**Funcionalidad**:

```php
// Eliminar (soft delete)
$producto->delete(); // Marca eliminado = true

// Verificar si está eliminado
if ($producto->trashed()) {
    // Está eliminado
}

// Restaurar
$producto->restore(); // Marca eliminado = false

// Queries con eliminados
Producto::withTrashed()->get(); // Incluye eliminados
Producto::onlyTrashed()->get(); // Solo eliminados
Producto::all(); // Excluye eliminados automáticamente

// Force delete (eliminar permanentemente)
$producto->forceDelete();
```

**Ventajas**:

* ✅ Usa el campo `eliminado` existente
* ✅ Filtrado automático de registros eliminados
* ✅ API compatible con SoftDeletes de Laravel
* ✅ No requiere cambios en base de datos

***

### 2. HasAuditFields

**Ubicación**: `app/Traits/HasAuditFields.php`

**Propósito**: Registra automáticamente quién creó, actualizó o eliminó cada registro + auditoría completa en tabla `auditoria_actividades`.

**Funcionalidad**:

```php
// Automático al crear
$producto = Producto::create([...]);
// Se registra en auditoria_actividades:
// - accion: 'crear'
// - usuario_id: auth()->id()
// - datos_nuevos: {...}
// - ip_address, user_agent

// Automático al actualizar
$producto->precio_venta = 150.00;
$producto->save();
// Se registra en auditoria_actividades:
// - accion: 'actualizar'
// - datos_anteriores: {precio_venta: 100.00}
// - datos_nuevos: {precio_venta: 150.00}

// Automático al eliminar
$producto->delete();
// Se registra en auditoria_actividades:
// - accion: 'eliminar'

// Automático al restaurar
$producto->restore();
// Se registra en auditoria_actividades:
// - accion: 'restaurar'

// Ver historial
$historial = $producto->historialAuditoria();
foreach ($historial as $cambio) {
    echo "{$cambio->accion} por {$cambio->usuario->nombre} el {$cambio->creado_en}";
}
```

**Qué se registra**:

* ✅ Usuario que hizo el cambio
* ✅ Empresa asociada
* ✅ Acción (crear, actualizar, eliminar, restaurar)
* ✅ Tabla y ID del registro
* ✅ Datos anteriores y nuevos (JSON)
* ✅ IP address
* ✅ User agent

**Ventajas**:

* ✅ Trazabilidad completa automática
* ✅ No requiere código adicional
* ✅ Protege campos sensibles (passwords, etc.)
* ✅ Usa tabla `auditoria_actividades` existente

***

### 3. HasActiveScope

**Ubicación**: `app/Traits/HasActiveScope.php`

**Propósito**: Facilita filtrado de registros activos/inactivos.

**Funcionalidad**:

```php
// Solo activos
$productos = Producto::activo()->get();

// Solo inactivos
$productos = Producto::inactivo()->get();

// Activar/desactivar
$producto->activar();
$producto->desactivar();
$producto->toggleActivo();

// Verificar estado
if ($producto->estaActivo()) {
    // Está activo
}

// Combinar con otros scopes
$productosActivos = Producto::activo()
    ->where('categoria_id', 5)
    ->orderBy('nombre')
    ->get();
```

**Ventajas**:

* ✅ Queries más legibles
* ✅ Métodos consistentes entre modelos
* ✅ Reduce código repetitivo

***

## 📦 Modelos Actualizados

Los siguientes modelos ya tienen los traits aplicados:

* ✅ `Producto`
* ✅ `Cliente`
* ✅ `Proveedor`
* ✅ `Venta`

**Para aplicar a otros modelos**:

```php
use App\Traits\HasCustomSoftDeletes;
use App\Traits\HasAuditFields;
use App\Traits\HasActiveScope;

class MiModelo extends Model
{
    use HasCustomSoftDeletes, HasAuditFields, HasActiveScope;
    // ...
}
```

***

## 💡 Ejemplos de Uso

### Ejemplo 1: CRUD con Auditoría Automática

```php
// Crear producto
$producto = Producto::create([
    'empresa_id' => 1,
    'nombre' => 'Laptop Dell',
    'precio_venta' => 1200.00,
    // ...
]);
// ✅ Se registra automáticamente en auditoria_actividades

// Actualizar
$producto->update(['precio_venta' => 1100.00]);
// ✅ Se registra cambio de precio con valores anterior y nuevo

// Eliminar (soft)
$producto->delete();
// ✅ Se marca eliminado = true y se registra en auditoría

// Listar productos (automáticamente excluye eliminados)
$productos = Producto::all();

// Incluir eliminados si es necesario
$todosLosProductos = Producto::withTrashed()->get();

// Restaurar
$producto->restore();
// ✅ Se registra restauración en auditoría
```

### Ejemplo 2: Consultas Optimizadas

```php
// Productos activos de una categoría (query legible)
$productos = Producto::activo()
    ->where('categoria_id', 5)
    ->where('stock_actual', '>', 0)
    ->orderBy('nombre')
    ->get();

// Clientes inactivos para reactivación
$clientesInactivos = Cliente::inactivo()
    ->where('ultima_compra', '<', now()->subMonths(6))
    ->get();
```

### Ejemplo 3: Auditoría y Trazabilidad

```php
// Ver historial completo de cambios
$producto = Producto::find(100);
$historial = $producto->historialAuditoria();

foreach ($historial as $auditoria) {
    echo "Acción: {$auditoria->accion}\n";
    echo "Usuario: {$auditoria->usuario->nombre}\n";
    echo "Fecha: {$auditoria->creado_en}\n";
    echo "IP: {$auditoria->ip_address}\n";
    
    if ($auditoria->datos_anteriores) {
        echo "Antes: " . json_encode($auditoria->datos_anteriores) . "\n";
    }
    if ($auditoria->datos_nuevos) {
        echo "Después: " . json_encode($auditoria->datos_nuevos) . "\n";
    }
    echo "---\n";
}
```

### Ejemplo 4: Restauración Masiva

```php
// Restaurar productos eliminados en los últimos 7 días
$productosRecientes = Producto::onlyTrashed()
    ->where('actualizado_en', '>=', now()->subDays(7))
    ->get();

foreach ($productosRecientes as $producto) {
    $producto->restore();
    // Se registra cada restauración en auditoría
}
```

***

## 🔍 Verificación de Funcionamiento

### Test Manual

```php
// En tinker o test
php artisan tinker

// 1. Probar soft delete
$producto = Producto::first();
$producto->delete();
$producto->trashed(); // true

// 2. Verificar que se registró en auditoría
$auditoria = AuditoriaActividad::latest()->first();
echo $auditoria->accion; // "eliminar"
echo $auditoria->tabla; // "productos"

// 3. Probar restauración
$producto->restore();
$producto->trashed(); // false

// 4. Ver historial
$producto->historialAuditoria();
```

### Tests Automatizados

Se recomienda agregar tests en `tests/Feature/`:

```php
public function test_soft_delete_registra_auditoria()
{
    $producto = Producto::factory()->create();
    
    $producto->delete();
    
    $this->assertTrue($producto->trashed());
    $this->assertDatabaseHas('auditoria_actividades', [
        'tabla' => 'productos',
        'registro_id' => $producto->id,
        'accion' => 'eliminar',
    ]);
}

public function test_restore_registra_auditoria()
{
    $producto = Producto::factory()->create();
    $producto->delete();
    
    $producto->restore();
    
    $this->assertFalse($producto->trashed());
    $this->assertDatabaseHas('auditoria_actividades', [
        'tabla' => 'productos',
        'registro_id' => $producto->id,
        'accion' => 'restaurar',
    ]);
}
```

***

## ⚙️ Configuración Avanzada

### Desactivar Auditoría para Modelos Específicos

Si un modelo no necesita auditoría:

```php
class LogInterno extends Model
{
    use HasCustomSoftDeletes; // Solo soft deletes
    use HasActiveScope;       // Solo scopes
    // NO usar HasAuditFields
}
```

### Customizar Campos de Auditoría

El trait detecta automáticamente si existen campos `created_by`, `updated_by`, `deleted_by`:

```php
// Si agregas estos campos en el futuro (migración)
Schema::table('productos', function (Blueprint $table) {
    $table->unsignedInteger('created_by')->nullable();
    $table->unsignedInteger('updated_by')->nullable();
    $table->unsignedInteger('deleted_by')->nullable();
    
    $table->foreign('created_by')->references('id')->on('usuarios');
    $table->foreign('updated_by')->references('id')->on('usuarios');
    $table->foreign('deleted_by')->references('id')->on('usuarios');
});

// El trait automáticamente los usará (sin cambiar código)
```

### Excluir Campos de Auditoría

El trait ya excluye campos sensibles:

* `password_hash`
* `pin_llave_fe_hash`
* `certificado_llave_fe`

Para agregar más:

```php
// En HasAuditFields.php, línea 90
$camposSensibles = [
    'password_hash', 
    'pin_llave_fe_hash', 
    'certificado_llave_fe',
    'numero_tarjeta',  // Agregar más según necesidad
    'cvv',
];
```

***

## 📊 Beneficios Obtenidos

| Funcionalidad           | Antes                       | Ahora                                      |
| ----------------------- | --------------------------- | ------------------------------------------ |
| **Soft Deletes**        | Manual con `eliminado`      | Automático con `delete()`                  |
| **Filtrado eliminados** | `where('eliminado', false)` | Automático (Global Scope)                  |
| **Auditoría**           | Manual                      | Automática en todas las operaciones        |
| **Trazabilidad**        | Limitada                    | Completa (quién, qué, cuándo, desde dónde) |
| **Historial**           | No disponible               | Método `historialAuditoria()`              |
| **Queries legibles**    | Complejas                   | `Producto::activo()->get()`                |
| **Restauración**        | Compleja                    | `$model->restore()`                        |

***

## 🎯 Próximos Pasos Recomendados

1. **Aplicar traits a más modelos**
   * Sucursal, Almacen, Empleado, etc.
2. **Crear comando Artisan para limpieza**

   ```php
   php artisan auditoria:limpiar --dias=90
   // Archiva registros de auditoría > 90 días
   ```
3. **Dashboard de Auditoría**
   * Vista de actividad reciente
   * Filtros por usuario, acción, tabla
   * Reportes de cambios
4. **Notificaciones de cambios críticos**

   ```php
   // En HasAuditFields, después de registrar
   if ($accion === 'eliminar' && $this->monto_total > 10000) {
       Notification::send($adminUsers, new RegistroEliminadoNotification($this));
   }
   ```

***

## 🐛 Troubleshooting

### Error: "Class AuditoriaActividad not found"

Asegúrate de que el modelo existe:

```php
php artisan make:model AuditoriaActividad
```

### Los registros eliminados siguen apareciendo

Verifica que el modelo tenga el trait:

```php
use HasCustomSoftDeletes;
```

### No se registra en auditoría

1. Verifica que el usuario esté autenticado
2. Verifica que el modelo tenga `empresa_id`
3. Revisa logs: `storage/logs/laravel.log`

***

## 📝 Notas Técnicas

* **Performance**: Los traits agregan \~5-10ms por operación (aceptable)
* **Storage**: Auditoría usa \~500 bytes por cambio
* **Compatibilidad**: Laravel 11+, MySQL 5.7+, PHP 8.2+
* **Testing**: Todos los tests existentes siguen pasando ✅

***

**Implementado por**: GitHub Copilot\
**Fecha**: 22 de noviembre de 2025\
**Versión**: 1.1.0


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://senselab.gitbook.io/senselab-docs/docs/archive/database_improvements.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
