Introducción
En aplicaciones empresariales, “guardar datos” no es suficiente. También necesitas saber cuándo se creó o modificó un registro, quién lo hizo y si puede recuperarse tras una eliminación lógica. En Laravel, esto se resuelve con tres capacidades complementarias:
Timestamps: marcan los momentos de creación/actualización.
Userstamps: registran el usuario responsable de crear/editar/eliminar.
Soft Deletes: permiten “eliminar” sin borrar físicamente, habilitando restauración y auditoría posterior.
Combinadas, estas técnicas ofrecen una trazabilidad completa con un costo de implementación bajo y gran impacto en cumplimiento, auditoría forense y soporte operativo. La idea central coincide con el enfoque del artículo original de Ilyas Kazi sobre el “quién, cuándo y por qué” de los cambios en Eloquent.
1. Timestamps en Eloquent (created_at y updated_at)
¿Qué son?
Laravel añade automáticamente
created_at y updated_at en modelos Eloquent cuando la tabla tiene esos campos tipo timestamp o datetime. Esta característica viene habilitada por defecto y cubre el cuándo. Documentación y artículos del ecosistema lo enfatizan como base de auditoría. Migración mínima
Schema::create('posts', function (Blueprint $table) { $table->id(); $table->string('title'); $table->text('body')->nullable(); $table->timestamps(); // created_at, updated_at });
Deshabilitar o personalizar
class Post extends Model { public $timestamps = false; // deshabilita timestamps // Si necesitas nombres personalizados: const CREATED_AT = 'creado_en'; const UPDATED_AT = 'actualizado_en'; }
Buenas prácticas
Define índices según tus consultas (por ejemplo,
para reportes cronológicos).created_atEvita mutarlos manualmente; si lo haces (importaciones o fixtures), centraliza la lógica.
2. Userstamps (created_by, updated_by, deleted_by)
¿Qué son?
Los userstamps guardan el quién realizó una acción sobre el modelo (creación, actualización y, si usas SoftDeletes, eliminación lógica). No forman parte del núcleo de Laravel, pero existen paquetes y enfoques consolidados.
Opción A: Paquete listo para usar
Un paquete popular es wildside/userstamps (a veces referenciado como Laravel Userstamps). Mantiene
created_by, updated_by y deleted_by automáticamente usando el usuario autenticado. Requiere Laravel 9+ y PHP 8.2+. Instalación
composer require wildside/userstamps
Migración típica
Schema::table('posts', function (Blueprint $table) { $table->foreignId('created_by')->nullable()->constrained('users'); $table->foreignId('updated_by')->nullable()->constrained('users'); $table->foreignId('deleted_by')->nullable()->constrained('users'); });
Uso en el modelo
use Wildside\Userstamps\Userstamps; use Illuminate\Database\Eloquent\SoftDeletes; class Post extends Model { use Userstamps, SoftDeletes; // El trait completará created_by, updated_by y, con SoftDeletes, deleted_by. }
Alternativas: Existen otros paquetes con la misma idea (p. ej.,
sqits/laravel-userstamps, tobidsn/laravel-userstamps). Evalúa mantenimiento, compatibilidad de versión y comunidad. Opción B: Implementación manual con Observers
Si prefieres cero dependencias, puedes usar Model Observers para setear los campos con
auth()->id():class PostObserver { public function creating(Post $post): void { if (auth()->check()) { $post->created_by = auth()->id(); $post->updated_by = auth()->id(); } } public function updating(Post $post): void { if (auth()->check()) { $post->updated_by = auth()->id(); } } public function deleting(Post $post): void { if (in_array(SoftDeletes::class, class_uses_recursive($post)) && auth()->check()) { $post->deleted_by = auth()->id(); // No llames save() aquí para no disparar más eventos; // rely on SoftDeletes para persistir. } } }
Registro del observer
// AppServiceProvider::boot() Post::observe(PostObserver::class);
Buenas prácticas
Usa
y llaves foráneas conforeignId
para no bloquear borrados de usuarios.onDelete('set null')Añade índices a
,created_by
,updated_by
si generarás reportes por usuario.deleted_by
3. Soft Deletes (deleted_at) y el “por qué” de restaurar
¿Qué son?
Soft Deletes marcan un registro como eliminado al establecer
deleted_at sin borrarlo físicamente. Esto habilita recuperación, auditoría y compliance. La comunidad y recursos oficiales recomiendan su uso cuando el negocio exige trazabilidad y reversibilidad. Laravel News+2Medium+2Migración
Schema::table('posts', function (Blueprint $table) { $table->softDeletes(); // agrega deleted_at });
Modelo
use Illuminate\Database\Eloquent\SoftDeletes; class Post extends Model { use SoftDeletes; }
Consultas frecuentes
Post::withTrashed()->find($id); // incluir eliminados Post::onlyTrashed()->get(); // solo eliminados Post::find($id)->restore(); // restaurar Post::find($id)->forceDelete(); // eliminación física
Estos métodos (
withTrashed, onlyTrashed, restore) forman parte del flujo estándar de SoftDeletes en artículos técnicos reconocidos. MediumBuenas prácticas
Evita usar booleanos (“deleted”) para emular soft delete; Laravel espera
, aunque es posible redefinirlo con trabajo adicional. Stack Overflowdeleted_atConsidera políticas de retención: cuándo permitir
y cuándo exigirrestore()
por normativa.forceDelete()
4. Integración conjunta: timestamps + userstamps + soft deletes
Patrón de auditoría mínima viable (MVP)
en todas las tablas transaccionales.timestamps()
en recursos críticos (clientes, pedidos, facturas, catálogos con histórico).softDeletes()
,created_by
,updated_by
con paquete o observers.deleted_by
class Post extends Model { use SoftDeletes; // Si usas paquete: use \Wildside\Userstamps\Userstamps; protected $fillable = ['title', 'body']; }
Consultas tipo “quién cambió qué y cuándo”
// Últimos cambios por usuario Post::query() ->whereNotNull('updated_by') ->with(['updater:id,name']) // relación belongsTo('updated_by') ->orderByDesc('updated_at') ->limit(20) ->get();
Relaciones sugeridas
class Post extends Model { public function creator() { return $this->belongsTo(User::class, 'created_by'); } public function updater() { return $this->belongsTo(User::class, 'updated_by'); } public function deleter() { return $this->belongsTo(User::class, 'deleted_by'); } }
5. Seguridad y cumplimiento
Impersonación: si usas impersonate, registra también el usuario real en un campo adicional o en el contexto de logs.
Privacidad: protege columnas de auditoría en serializaciones públicas (
en el modelo) para APIs expuestas.hiddenPolíticas (Policies): controla quién puede
orestore
.forceDeleteMigraciones: aplica
anullable()
para evitar inconsistencias cuando se elimina un usuario.deleted_byIntegridad: añade índices compuestos para consultas frecuentes (p. ej.,
).(deleted_at, updated_at)
6. Rendimiento y escalabilidad
Índices:
debe estar indexado si usasdeleted_at
con frecuencia.whereNull('deleted_at')Carga diferida: evita
indiscriminado.withTrashed()Batching: en limpiezas periódicas, usa
conchunkById()
para no bloquear.forceDelete()
7. Testing de auditoría
Prueba de creación
public function test_creates_with_userstamps(): void { $user = User::factory()->create(); $this->be($user); $post = Post::create(['title' => 'T', 'body' => 'B']); $this->assertNotNull($post->created_at); $this->assertEquals($user->id, $post->created_by); }
Prueba de soft delete y restore
public function test_soft_delete_and_restore(): void { $user = User::factory()->create(); $this->be($user); $post = Post::factory()->create(); $post->delete(); $this->assertNotNull($post->fresh()->deleted_at); $this->assertEquals($user->id, $post->deleted_by); $post->restore(); $this->assertNull($post->fresh()->deleted_at); }
8. Paquetes y referencias
Artículo original sobre el enfoque “quién/cuándo/por qué” con timestamps, userstamps y soft deletes. Medium
Paquete Laravel Userstamps (wildside): instalación y uso. GitHub+1
Notas y ejemplos de userstamps en el ecosistema Laravel News. Laravel News
Soft Deletes en la práctica y métodos
,withTrashed
,onlyTrashed
. MediumrestoreConsideraciones y guía general de Soft Deletes en el ecosistema Laravel. Laravel News+1
Conclusión
Para una auditoría robusta en Laravel, aplica timestamps como estándar, añade soft deletes donde la recuperación sea requisito funcional y habilita userstamps con un paquete maduro o con observers si necesitas control total. Este trinomio te aporta trazabilidad clara del cuándo, quién y por qué, sin comprometer el rendimiento ni la mantenibilidad.