This commit is contained in:
Lizandro Guarnizo
2026-01-06 15:35:59 -05:00
commit a768146f65
602 changed files with 42505 additions and 0 deletions
+171
View File
@@ -0,0 +1,171 @@
<?php
namespace App\Observers;
use App\Models\Compra;
use App\Models\Producto;
use App\Models\ProductVariant;
use App\Models\Bodega;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\DB;
class CompraObserver
{
/**
* Handle the Compra "created" event.
*/
public function created(Compra $compra): void
{
//
}
/**
* Handle the Compra "updated" event.
*/
public function updated(Compra $compra): void
{
// Solo procesar cuando el estado cambie a 'Recibida'
if ($compra->isDirty('estado') && $compra->estado === 'Recibida') {
Log::info("Procesando compra recibida", [
'compra_id' => $compra->id,
'detalles_count' => $compra->detalles->count()
]);
DB::beginTransaction();
try {
foreach ($compra->detalles as $detalle) {
Log::info("Procesando detalle de compra", [
'detalle_id' => $detalle->id,
'producto_id' => $detalle->producto_id,
'variante_id' => $detalle->variante_id,
'bodega_id' => $detalle->bodega_id,
'cantidad' => $detalle->cantidad
]);
if ($detalle->variante_id) {
// Manejo para variantes
$variante = ProductVariant::find($detalle->variante_id);
if ($variante && $detalle->bodega_id) {
// Actualizar stock en bodega para la variante
$existeRelacion = $variante->bodegas()
->where('bodega_id', $detalle->bodega_id)
->exists();
if ($existeRelacion) {
// Incrementar stock existente
$pivotData = $variante->bodegas()
->where('bodega_id', $detalle->bodega_id)
->first();
$nuevoStock = $pivotData->pivot->stock + $detalle->cantidad;
$variante->bodegas()->updateExistingPivot($detalle->bodega_id, [
'stock' => $nuevoStock
]);
} else {
// Crear nueva relación con stock
$variante->bodegas()->attach($detalle->bodega_id, [
'stock' => $detalle->cantidad
]);
}
Log::info("Stock actualizado para variante en bodega", [
'variante_id' => $variante->id,
'bodega_id' => $detalle->bodega_id,
'cantidad_agregada' => $detalle->cantidad
]);
} else {
// Fallback: incrementar stock directo de la variante si no hay bodega especificada
ProductVariant::where('id', $detalle->variante_id)
->increment('stock', $detalle->cantidad);
Log::info("Stock incrementado directamente en variante (sin bodega)", [
'variante_id' => $detalle->variante_id,
'cantidad' => $detalle->cantidad
]);
}
} else {
// Manejo para productos sin variantes
$producto = Producto::find($detalle->producto_id);
if ($producto && $detalle->bodega_id) {
// Actualizar stock en bodega para el producto
$existeRelacion = $producto->bodegas()
->where('bodega_id', $detalle->bodega_id)
->exists();
if ($existeRelacion) {
// Incrementar stock existente
$pivotData = $producto->bodegas()
->where('bodega_id', $detalle->bodega_id)
->first();
$nuevoStock = $pivotData->pivot->stock + $detalle->cantidad;
$producto->bodegas()->updateExistingPivot($detalle->bodega_id, [
'stock' => $nuevoStock
]);
} else {
// Crear nueva relación con stock
$producto->bodegas()->attach($detalle->bodega_id, [
'stock' => $detalle->cantidad
]);
}
Log::info("Stock actualizado para producto en bodega", [
'producto_id' => $producto->id,
'bodega_id' => $detalle->bodega_id,
'cantidad_agregada' => $detalle->cantidad
]);
} else {
// Fallback: incrementar stock directo del producto si no hay bodega especificada
Producto::where('id', $detalle->producto_id)
->increment('stock', $detalle->cantidad);
Log::info("Stock incrementado directamente en producto (sin bodega)", [
'producto_id' => $detalle->producto_id,
'cantidad' => $detalle->cantidad
]);
}
}
}
DB::commit();
Log::info("Compra procesada exitosamente", ['compra_id' => $compra->id]);
} catch (\Exception $e) {
DB::rollback();
Log::error("Error al procesar compra recibida", [
'compra_id' => $compra->id,
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString()
]);
throw $e;
}
}
}
/**
* Handle the Compra "deleted" event.
*/
public function deleted(Compra $compra): void
{
//
}
/**
* Handle the Compra "restored" event.
*/
public function restored(Compra $compra): void
{
//
}
/**
* Handle the Compra "force deleted" event.
*/
public function forceDeleted(Compra $compra): void
{
//
}
}
+41
View File
@@ -0,0 +1,41 @@
<?php
namespace App\Observers;
use App\Models\DetalleCompra;
use Illuminate\Support\Facades\Log;
class DetalleCompraObserver
{
/**
* Handle the DetalleCompra "saving" event.
*/
public function saving(DetalleCompra $detalle): void
{
// Guardar snapshot del producto si existe y no hay snapshot previo
if ($detalle->producto && empty($detalle->producto_nombre_snapshot)) {
$detalle->producto_nombre_snapshot = $detalle->producto->nombre;
Log::info("Snapshot de producto guardado", [
'detalle_id' => $detalle->id,
'producto_id' => $detalle->producto_id,
'producto_nombre' => $detalle->producto_nombre_snapshot
]);
}
// Guardar snapshot de variante si existe y no hay snapshot previo
if ($detalle->variante && empty($detalle->variante_info_snapshot)) {
// Validación defensiva para evitar error "name" on null
$colorName = $detalle->variante->color ? $detalle->variante->color->name : 'Sin color';
$sizeName = $detalle->variante->size ? $detalle->variante->size->name : 'Sin talla';
$detalle->variante_info_snapshot = $colorName . ' / ' . $sizeName;
Log::info("Snapshot de variante guardado", [
'detalle_id' => $detalle->id,
'variante_id' => $detalle->variante_id,
'variante_info' => $detalle->variante_info_snapshot
]);
}
}
}
+66
View File
@@ -0,0 +1,66 @@
<?php
namespace App\Observers;
use App\Models\ProductVariant;
use Illuminate\Support\Facades\Log;
class ProductVariantObserver
{
/**
* Handle the ProductVariant "created" event.
*/
public function created(ProductVariant $variant): void
{
$producto = $variant->producto;
Log::info("Variante creada para producto", [
'variante_id' => $variant->id,
'producto_id' => $producto->id,
'producto_nombre' => $producto->nombre,
'stock_producto_anterior' => $producto->stock
]);
// Si este producto ahora tiene variantes y tenía stock directo, limpiar el stock principal
if ($producto->stock > 0) {
// Obtener el stock anterior para potencial transferencia
$stockAnterior = $producto->stock;
// Limpiar el stock del producto principal
$producto->update(['stock' => 0]);
Log::info("Stock del producto principal limpiado debido a creación de variantes", [
'producto_id' => $producto->id,
'stock_anterior' => $stockAnterior,
'stock_nuevo' => 0
]);
// Opcionalmente, podrías transferir el stock anterior a la nueva variante
// o a una bodega específica, pero por ahora solo lo eliminamos
}
}
/**
* Handle the ProductVariant "deleting" event.
*/
public function deleting(ProductVariant $variant): void
{
$producto = $variant->producto;
// Verificar si esta es la última variante
$variantesRestantes = $producto->variants()->where('id', '!=', $variant->id)->count();
Log::info("Eliminando variante", [
'variante_id' => $variant->id,
'producto_id' => $producto->id,
'variantes_restantes' => $variantesRestantes
]);
// Si esta era la última variante, el producto vuelve a poder tener stock directo
if ($variantesRestantes === 0) {
Log::info("Última variante eliminada - producto puede volver a tener stock directo", [
'producto_id' => $producto->id
]);
}
}
}
+69
View File
@@ -0,0 +1,69 @@
<?php
namespace App\Observers;
use App\Models\Producto;
use App\Models\User;
use App\Notifications\StockMinimoNotificacion;
use App\Mail\AlertaStockMail;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Mail;
class ProductoObserver
{
public function saved(Producto $producto): void
{
// Solo verificar si no es un producto recién creado
if ($producto->wasRecentlyCreated) {
return;
}
$stockActual = $producto->getStockEfectivo();
$alertaStockMinimo = collect();
$alertaStockMaximo = collect();
// Verificar stock mínimo
if ($stockActual <= $producto->stock_minimo && $producto->stock_minimo > 0) {
$alertaStockMinimo->push($producto);
Log::info("Stock mínimo alcanzado para producto: {$producto->nombre} (Stock: {$stockActual})");
}
// Verificar stock máximo
if ($producto->stock_maximo && $stockActual > $producto->stock_maximo) {
$alertaStockMaximo->push($producto);
Log::info("Stock máximo excedido para producto: {$producto->nombre} (Stock: {$stockActual})");
}
// Enviar alertas si hay productos que requieren atención
if ($alertaStockMinimo->isNotEmpty() || $alertaStockMaximo->isNotEmpty()) {
$this->enviarAlertasStock($alertaStockMinimo, $alertaStockMaximo);
}
}
private function enviarAlertasStock($productosStockMinimo, $productosStockMaximo)
{
// Obtener todos los usuarios con el rol 'Administrador'
$administradores = User::role('Administrador')->get();
if ($administradores->isEmpty()) {
Log::warning('No se encontraron usuarios con el rol Administrador para enviar alertas de stock.');
return;
}
foreach ($administradores as $admin) {
try {
// Enviar usando el nuevo sistema de alertas
Mail::to($admin->email)->send(new AlertaStockMail(
$productosStockMinimo,
$productosStockMaximo,
$admin->email
));
Log::info("Alerta de stock enviada a: {$admin->email}");
} catch (\Exception $e) {
Log::error("Error al enviar alerta de stock a {$admin->email}: " . $e->getMessage());
}
}
}
}