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
+68
View File
@@ -0,0 +1,68 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use App\Models\ProductVariant;
class Bodega extends Model
{
use HasFactory;
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'nombre',
];
/**
* The attributes that should be cast to native types.
*
* @var array
*/
protected $casts = [
'id' => 'integer',
];
/**
* Relación manytomany con productos.
*/
public function productos(): BelongsToMany
{
return $this->belongsToMany(Producto::class, 'producto_bodega')
->withPivot('stock')
->withTimestamps();
}
/**
* Relación many-to-many con variantes.
*/
public function variantes(): BelongsToMany
{
return $this->belongsToMany(ProductVariant::class, 'variante_bodega', 'bodega_id', 'variante_id')
->withPivot('stock')
->withTimestamps();
}
/**
* Obtener el stock total de todos los productos en esta bodega.
*/
public function getStockTotalAttribute(): int
{
return $this->productos()->sum('producto_bodega.stock');
}
/**
* Obtener el stock de un producto específico en esta bodega.
*/
public function getStockProducto(int $productoId): int
{
$producto = $this->productos()->where('producto_id', $productoId)->first();
return $producto ? $producto->pivot->stock : 0;
}
}
+49
View File
@@ -0,0 +1,49 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Caja extends Model
{
use HasFactory;
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'monto_inicial',
'monto_final',
'fecha_apertura',
'fecha_cierre',
'estado',
];
/**
* The attributes that should be cast to native types.
*
* @var array
*/
protected $casts = [
'id' => 'integer',
'monto_inicial' => 'decimal:2',
'monto_final' => 'decimal:2',
'fecha_apertura' => 'datetime',
'fecha_cierre' => 'datetime',
];
public function movimientoscaja()
{
return $this->hasMany(MovimientoCaja::class);
}
// Método para calcular el monto final
public function calcularMontoFinal()
{
$ingresos = $this->movimientoscaja()->where('tipo', 'Ingreso')->sum('monto');
$egresos = $this->movimientoscaja()->where('tipo', 'Egreso')->sum('monto');
return $this->monto_inicial + $ingresos - $egresos;
}
}
+30
View File
@@ -0,0 +1,30 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Categoria extends Model
{
use HasFactory;
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'nombre',
'descripcion',
];
/**
* The attributes that should be cast to native types.
*
* @var array
*/
protected $casts = [
'id' => 'integer',
];
}
+32
View File
@@ -0,0 +1,32 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Cliente extends Model
{
use HasFactory;
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'nombre',
'correo',
'telefono',
'numero_documento',
];
/**
* The attributes that should be cast to native types.
*
* @var array
*/
protected $casts = [
'id' => 'integer',
];
}
+30
View File
@@ -0,0 +1,30 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Color extends Model
{
use HasFactory;
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'name',
'hex_code',
];
/**
* The attributes that should be cast to native types.
*
* @var array
*/
protected $casts = [
'id' => 'integer',
];
}
+60
View File
@@ -0,0 +1,60 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class Compra extends Model
{
use HasFactory;
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'fecha',
'proveedor_id',
'total',
'estado',
];
/**
* The attributes that should be cast to native types.
*
* @var array
*/
protected $casts = [
'id' => 'integer',
'fecha' => 'datetime',
'proveedor_id' => 'integer',
'total' => 'decimal:2',
];
public function proveedor(): BelongsTo
{
return $this->belongsTo(Proveedor::class);
}
public function detalles()
{
return $this->hasMany(DetalleCompra::class);
}
/**
* Boot del modelo para eventos
*/
protected static function boot()
{
parent::boot();
// Al eliminar una compra, eliminar sus detalles
static::deleting(function ($compra) {
$compra->detalles()->delete();
});
}
}
+94
View File
@@ -0,0 +1,94 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class DetalleCompra extends Model
{
use HasFactory;
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'compra_id',
'producto_id',
'variante_id',
'bodega_id',
'cantidad',
'precio_unitario',
'subtotal',
'producto_nombre_snapshot',
'variante_info_snapshot',
'DetalleCompra',
];
/**
* The attributes that should be cast to native types.
*
* @var array
*/
protected $casts = [
'id' => 'integer',
'compra_id' => 'integer',
'producto_id' => 'integer',
'variante_id' => 'integer',
'bodega_id' => 'integer',
'cantidad'=> 'integer',
'precio_unitario' => 'decimal:2',
'subtotal' => 'decimal:2',
];
public function compra(): BelongsTo
{
return $this->belongsTo(Compra::class);
}
public function producto(): BelongsTo
{
return $this->belongsTo(Producto::class)->withDefault();
}
public function variante():BelongsTo
{
return $this->belongsTo(ProductVariant::class)->withDefault();
}
public function bodega(): BelongsTo
{
return $this->belongsTo(Bodega::class);
}
/**
* Obtiene el nombre del producto, usando snapshot si el producto fue eliminado
*/
public function getProductoNombreAttribute(): string
{
if ($this->producto) {
return $this->producto->nombre;
}
return $this->producto_nombre_snapshot ?? 'Producto eliminado';
}
/**
* Obtiene la información de la variante, usando snapshot si la variante fue eliminada
*/
public function getVarianteInfoAttribute(): ?string
{
if ($this->variante) {
// Validación defensiva para evitar error "name" on null
$colorName = $this->variante->color ? $this->variante->color->name : 'Sin color';
$sizeName = $this->variante->size ? $this->variante->size->name : 'Sin talla';
return $colorName . ' / ' . $sizeName;
}
return $this->variante_info_snapshot;
}
}
+62
View File
@@ -0,0 +1,62 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class DetalleVenta extends Model
{
use HasFactory;
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'venta_id',
'producto_id',
'variante_id', // <-- agregar aquí
'bodega_id', // <-- agregar campo bodega
'cantidad',
'precio_unitario',
'subtotal',
];
/**
* The attributes that should be cast to native types.
*
* @var array
*/
protected $casts = [
'id' => 'integer',
'venta_id' => 'integer',
'producto_id' => 'integer',
'precio_unitario' => 'decimal:2',
'subtotal' => 'decimal:2',
];
public function venta(): BelongsTo
{
return $this->belongsTo(Venta::class);
}
public function producto(): BelongsTo
{
return $this->belongsTo(Producto::class);
}
public function variante(): BelongsTo
{
return $this->belongsTo(ProductVariant::class);
}
public function bodega(): BelongsTo
{
return $this->belongsTo(Bodega::class);
}
}
+39
View File
@@ -0,0 +1,39 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class Inventario extends Model
{
use HasFactory;
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'producto_id',
'stock_actual',
'stock_minimo',
'ubicacion',
];
/**
* The attributes that should be cast to native types.
*
* @var array
*/
protected $casts = [
'id' => 'integer',
'producto_id' => 'integer',
];
public function producto(): BelongsTo
{
return $this->belongsTo(Producto::class);
}
}
+41
View File
@@ -0,0 +1,41 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class MovimientoCaja extends Model
{
use HasFactory;
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'caja_id',
'tipo',
'monto',
'descripcion',
];
/**
* The attributes that should be cast to native types.
*
* @var array
*/
protected $casts = [
'id' => 'integer',
'caja_id' => 'integer',
'monto' => 'decimal:2',
];
public function caja(): BelongsTo
{
return $this->belongsTo(Caja::class);
}
}
+24
View File
@@ -0,0 +1,24 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Spatie\Permission\Traits\HasRoles;
class Permission extends Model
{
use HasFactory, HasRoles;
protected $fillable = ['name', 'guard_name'];
protected $attributes = [
'guard_name' => 'web', // Establece el valor predeterminado
];
public function roles()
{
return $this->belongsToMany(Role::class, 'role_has_permissions');
}
}
+93
View File
@@ -0,0 +1,93 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
class ProductVariant extends Model
{
use HasFactory;
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'producto_id',
'color_id',
'size_id',
'stock',
'sku',
'barcode'
];
/**
* The attributes that should be cast to native types.
*
* @var array
*/
protected $casts = [
'id' => 'integer',
];
public function producto(): BelongsTo
{
return $this->belongsTo(Producto::class);
}
public function color(): BelongsTo
{
return $this->belongsTo(Color::class);
}
public function size(): BelongsTo
{
return $this->belongsTo(Size::class);
}
/**
* Relación many-to-many con bodegas
*/
public function bodegas(): BelongsToMany
{
return $this->belongsToMany(Bodega::class, 'variante_bodega', 'variante_id', 'bodega_id')
->withPivot('stock')
->withTimestamps();
}
/**
* Obtener el stock total de la variante en todas las bodegas
*/
public function getStockTotalBodegas(): int
{
try {
return $this->bodegas()->sum('variante_bodega.stock');
} catch (\Exception $e) {
// En caso de error, retornar 0
return 0;
}
}
/**
* Obtener el stock efectivo de la variante (considerando bodegas si existen)
*/
public function getStockEfectivo(): int
{
try {
// Si tiene distribución en bodegas, usar ese stock
if ($this->bodegas()->exists()) {
return $this->getStockTotalBodegas();
}
} catch (\Exception $e) {
// En caso de error (tabla no existe, etc.), usar stock directo
// Esto puede ocurrir durante migraciones o configuración inicial
}
// Stock directo de la variante
return $this->stock;
}
}
+256
View File
@@ -0,0 +1,256 @@
<?php
namespace App\Models;
use Filament\Actions\Concerns\BelongsToGroup;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
class Producto extends Model
{
use HasFactory;
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'nombre',
'descripcion',
'codigo_barras',
'precio_compra',
'precio_venta',
'stock',
'categoria_id',
'estado',
'imagen',
'stock_minimo',
'stock_maximo',
'unidad_medida'
];
/**
* The attributes that should be cast to native types.
*
* @var array
*/
protected $casts = [
'id' => 'integer',
'estado' => 'boolean',
];
public function variants(): HasMany
{
return $this->hasMany(ProductVariant::class);
}
public function categoria(): BelongsTo
{
return $this->belongsTo(Categoria::class);
}
/**
* Relación many-to-many con bodegas
*/
public function bodegas(): BelongsToMany
{
return $this->belongsToMany(Bodega::class, 'producto_bodega')
->withPivot('stock')
->withTimestamps();
}
/**
* Obtiene las unidades de medida disponibles
*/
public static function getUnidadesMedida(): array
{
return [
'unidad' => 'Unidad (1)',
'docena' => 'Docena (12)',
];
}
/**
* Obtiene el factor de conversión para una unidad de medida
*/
public static function getFactorConversion(string $unidad): int
{
$factores = [
'unidad' => 1,
'docena' => 12,
];
return $factores[$unidad] ?? 1;
}
/**
* Convierte una cantidad en la unidad especificada a unidades individuales
*/
public static function convertirAUnidades(float $cantidad, string $unidad): int
{
return (int) ($cantidad * self::getFactorConversion($unidad));
}
/**
* Convierte unidades individuales a la unidad de medida especificada
*/
public static function convertirDesdeUnidades(int $unidades, string $unidad): float
{
$factor = self::getFactorConversion($unidad);
return $factor > 1 ? round($unidades / $factor, 2) : $unidades;
}
/**
* Obtiene el stock en la unidad de medida configurada
*/
public function getStockEnUnidadMedida(): float
{
$stockTotal = $this->variants()->exists()
? $this->variants()->sum('stock')
: $this->stock;
return self::convertirDesdeUnidades($stockTotal, $this->unidad_medida ?? 'unidad');
}
/**
* Obtiene el nombre de la unidad de medida
*/
public function getNombreUnidadMedida(): string
{
$unidades = self::getUnidadesMedida();
return $unidades[$this->unidad_medida ?? 'unidad'] ?? 'Unidad (1)';
}
/**
* Obtener el stock total en todas las bodegas
*/
public function getStockTotalBodegas(): int
{
return $this->bodegas()->sum('producto_bodega.stock');
}
/**
* Obtener el stock en una bodega específica
*/
public function getStockEnBodega(int $bodegaId): int
{
$bodega = $this->bodegas()->where('bodega_id', $bodegaId)->first();
return $bodega ? $bodega->pivot->stock : 0;
}
/**
* Obtener el stock total en una bodega específica (considerando variantes)
*/
public function getStockTotalEnBodega(int $bodegaId): int
{
if ($this->variants()->exists()) {
$stockVariantes = 0;
foreach ($this->variants as $variante) {
$bodegaVariante = $variante->bodegas()->where('bodega_id', $bodegaId)->first();
if ($bodegaVariante) {
$stockVariantes += $bodegaVariante->pivot->stock;
}
}
return $stockVariantes;
} else {
return $this->getStockEnBodega($bodegaId);
}
}
/**
* Distribuir stock total entre bodegas
*/
public function distribuirStock(array $distribucion): void
{
foreach ($distribucion as $bodegaId => $stock) {
$this->bodegas()->syncWithoutDetaching([
$bodegaId => ['stock' => $stock]
]);
}
}
/**
* Obtener el stock efectivo (considerando bodegas Y variantes correctamente)
*/
public function getStockEfectivo(): int
{
// Si tiene variantes, el stock se calcula desde las variantes
if ($this->variants()->exists()) {
$stockVariantes = 0;
foreach ($this->variants as $variante) {
$stockVariantes += $variante->getStockEfectivo();
}
return $stockVariantes;
} else {
// Para productos sin variantes
if ($this->bodegas()->exists()) {
return $this->getStockTotalBodegas();
} else {
return $this->stock;
}
}
}
/**
* Accessor para código de barras dinámico
*/
public function getCodigoBarrasAttribute($value)
{
// Si el producto tiene variantes, el código de barras es NULL
if ($this->variants()->exists()) {
return null;
}
// Si NO tiene variantes, usa su código de barras propio
return $value;
}
/**
* Accessor para stock dinámico
* Si el producto tiene variantes, el stock directo debe ser 0
*/
public function getStockAttribute($value)
{
// Si el producto tiene variantes, el stock directo debe ser 0
if ($this->variants()->exists()) {
return 0;
}
// Si NO tiene variantes, devuelve el stock real
return $value;
}
/**
* Mutator para stock - evita que se asigne stock directo si hay variantes
*/
public function setStockAttribute($value)
{
// Si el producto ya tiene variantes, no permitir stock directo
if ($this->exists && $this->variants()->exists()) {
$this->attributes['stock'] = 0;
} else {
$this->attributes['stock'] = $value;
}
}
/**
* Relación con detalles de compra
*/
public function detalleCompras(): HasMany
{
return $this->hasMany(DetalleCompra::class);
}
/**
* Relación con detalles de venta
*/
public function detalleVentas(): HasMany
{
return $this->hasMany(DetalleVenta::class);
}
}
+33
View File
@@ -0,0 +1,33 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Proveedor extends Model
{
use HasFactory;
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'nombre',
'contacto',
'telefono',
'correo',
'direccion',
];
/**
* The attributes that should be cast to native types.
*
* @var array
*/
protected $casts = [
'id' => 'integer',
];
}
+45
View File
@@ -0,0 +1,45 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Spatie\Permission\Models\Role as SpatieRole;
class Role extends SpatieRole
{
use HasFactory;
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'name',
'guard_name', // Necesario para Spatie Permissions
'description',
];
/**
* The attributes that should be cast to native types.
*
* @var array
*/
protected $casts = [
'id' => 'integer',
];
/**
* Get the users that belong to the role.
*/
public function users(): BelongsToMany
{
return $this->belongsToMany(User::class, 'model_has_roles', 'role_id', 'model_id');
}
public function permissions(): BelongsToMany
{
return $this->belongsToMany(Permission::class);
}
}
+34
View File
@@ -0,0 +1,34 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Setting extends Model
{
use HasFactory;
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'logo',
'description',
'primary_color',
'secondary_color',
];
/**
* The attributes that should be cast to native types.
*
* @var array
*/
protected $casts = [
'id' => 'integer',
'created_at' => 'timestamp',
'updated_at' => 'timestamp',
];
}
+29
View File
@@ -0,0 +1,29 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Size extends Model
{
use HasFactory;
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'name',
];
/**
* The attributes that should be cast to native types.
*
* @var array
*/
protected $casts = [
'id' => 'integer',
];
}
+69
View File
@@ -0,0 +1,69 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class TransferenciaBodega extends Model
{
use HasFactory;
protected $table = 'transferencias_bodega';
protected $fillable = [
'producto_id',
'variante_id',
'bodega_origen_id',
'bodega_destino_id',
'cantidad',
'motivo',
'usuario_id',
'fecha_transferencia',
];
protected $casts = [
'fecha_transferencia' => 'datetime',
];
/**
* Relación con el producto
*/
public function producto(): BelongsTo
{
return $this->belongsTo(Producto::class);
}
/**
* Relación con la variante (opcional)
*/
public function variante(): BelongsTo
{
return $this->belongsTo(ProductVariant::class, 'variante_id');
}
/**
* Relación con la bodega de origen
*/
public function bodegaOrigen(): BelongsTo
{
return $this->belongsTo(Bodega::class, 'bodega_origen_id');
}
/**
* Relación con la bodega de destino
*/
public function bodegaDestino(): BelongsTo
{
return $this->belongsTo(Bodega::class, 'bodega_destino_id');
}
/**
* Relación con el usuario que realizó la transferencia
*/
public function usuario(): BelongsTo
{
return $this->belongsTo(User::class, 'usuario_id');
}
}
+57
View File
@@ -0,0 +1,57 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Sanctum\HasApiTokens;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Support\Facades\Hash;
use Spatie\Permission\Traits\HasRoles;
class User extends Authenticatable
{
use HasApiTokens, HasFactory, Notifiable, HasRoles;
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'name',
'email',
'password',
];
/**
* The attributes that should be hidden for serialization.
*
* @var array
*/
protected $hidden = [
'password',
'remember_token',
];
/**
* The attributes that should be cast to native types.
*
* @var array
*/
protected $casts = [
'email_verified_at' => 'datetime',
'password' => 'hashed', // Laravel 10+ soporta casting a hashed
];
/**
* Get the roles associated with the user.
*/
public function roles(): BelongsToMany
{
return $this->belongsToMany(Role::class);
}
}
+48
View File
@@ -0,0 +1,48 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
class Venta extends Model
{
use HasFactory;
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'cliente_id',
'total',
'tipo_pago',
'estado',
];
/**
* The attributes that should be cast to native types.
*
* @var array
*/
protected $casts = [
'id' => 'integer',
'fecha' => 'datetime',
'cliente_id' => 'integer',
'total' => 'decimal:2',
];
public function cliente(): BelongsTo
{
return $this->belongsTo(Cliente::class);
}
public function detalles(): HasMany
{
return $this->hasMany(DetalleVenta::class);
}
}