HEX
Server: Apache/2.4.58 (Ubuntu)
System: Linux Bradford-Sitios 6.14.0-1017-azure #17~24.04.1-Ubuntu SMP Mon Dec 1 20:10:50 UTC 2025 x86_64
User: www-data (33)
PHP: 7.4.33
Disabled: pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,pcntl_unshare,
Upload Files
File: /var/www/api-talleres.bradford/app/Models/ClubOrder.php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class ClubOrder extends Model
{
    use HasFactory;

    protected $table = 'club_orders';
    protected $primaryKey = 'id';

    protected $fillable = [
        'code',
        'parent_id',
        'period_id',
        'status',
        'items_count',
        'subtotal',
        'discount_percent',
        'discount_amount',
        'total',
        'reservation_minutes',
        'expires_at',
        'paid_at',
        'confirmed_at',
        'failed_at',
        'payment_method',
        'payment_reference',
        'checkout_url',
        'payment_response',
        'log_id',
        'ip_address',
        'user_agent',
        'notes',
        'deleted',
        'deleted_at',
        'user_created',
        'user_updated',
        'user_deleted',
    ];

    protected $hidden = [
        'deleted',
        'deleted_at',
        'user_deleted',
        'payment_response',
        'user_agent',
    ];

    protected $casts = [
        'items_count' => 'integer',
        'subtotal' => 'decimal:0',
        'discount_percent' => 'decimal:2',
        'discount_amount' => 'decimal:0',
        'total' => 'decimal:0',
        'reservation_minutes' => 'integer',
        'expires_at' => 'datetime',
        'paid_at' => 'datetime',
        'confirmed_at' => 'datetime',
        'failed_at' => 'datetime',
    ];

    // =========================================================================
    // RELACIONES
    // =========================================================================

    /**
     * Apoderado que realiza el pedido.
     */
    public function parent()
    {
        return $this->belongsTo(Parents::class, 'parent_id');
    }

    /**
     * Período académico del pedido.
     */
    public function period()
    {
        return $this->belongsTo(Period::class, 'period_id');
    }

    /**
     * Items del pedido (inscripciones).
     */
    public function items()
    {
        return $this->hasMany(ClubOrderItem::class, 'club_order_id');
    }

    /**
     * Estado del pedido (desde catálogo).
     */
    public function statusInfo()
    {
        return $this->belongsTo(ClubOrderStatus::class, 'status', 'code');
    }

    /**
     * Webhook log asociado al pago de este pedido.
     */
    public function webhookLog()
    {
        return $this->belongsTo(WebhookLog::class, 'log_id');
    }

    /**
     * Transacción estructurada (a través del webhook log).
     */
    public function getTransactionAttribute()
    {
        return $this->webhookLog ? $this->webhookLog->transaction : null;
    }

    public function createdBy()
    {
        return $this->belongsTo(User::class, 'user_created');
    }

    public function updatedBy()
    {
        return $this->belongsTo(User::class, 'user_updated');
    }

    public function deletedBy()
    {
        return $this->belongsTo(User::class, 'user_deleted');
    }

    // =========================================================================
    // HELPERS
    // =========================================================================

    /**
     * Verifica si el pedido está expirado.
     */
    public function isExpired(): bool
    {
        return $this->status === 'expired' ||
            ($this->expires_at && now()->greaterThan($this->expires_at));
    }

    /**
     * Verifica si el pedido está activo (reserva en curso).
     */
    public function isActive(): bool
    {
        return $this->status === 'active' && !$this->isExpired();
    }

    /**
     * Verifica si el pedido está confirmado.
     */
    public function isConfirmed(): bool
    {
        return $this->status === 'confirmed';
    }

    /**
     * Verifica si el pedido está pagado.
     */
    public function isPaid(): bool
    {
        return in_array($this->status, ['paid', 'confirmed']);
    }

    /**
     * Verifica si el pedido falló.
     */
    public function isFailed(): bool
    {
        return $this->status === 'failed';
    }

    /**
     * Verifica si el pedido es gratuito (total = 0).
     */
    public function isFree(): bool
    {
        return $this->total == 0;
    }

    /**
     * Obtiene los segundos restantes de la reserva.
     */
    public function getRemainingSeconds(): int
    {
        if (!$this->expires_at || $this->isExpired()) {
            return 0;
        }
        return max(0, now()->diffInSeconds($this->expires_at, false));
    }

    /**
     * Recalcula los totales del pedido.
     */
    public function recalculateTotals(): void
    {
        $items = $this->items()->whereNull('cancelled_at')->get();

        $this->items_count = $items->count();
        $this->subtotal = $items->sum('price_snapshot');
        $this->discount_percent = null;
        $this->discount_amount = 0;
        $this->total = $this->subtotal;
    }

    // =========================================================================
    // SCOPES
    // =========================================================================

    /**
     * Pedidos activos (con reserva en curso).
     */
    public function scopeActive($query)
    {
        return $query->where('status', 'active')
            ->where('expires_at', '>', now());
    }

    /**
     * Pedidos expirados pendientes de procesar.
     */
    public function scopeExpiredPending($query)
    {
        return $query->where('status', 'active')
            ->where('expires_at', '<=', now());
    }

    /**
     * Pedidos de un apoderado.
     */
    public function scopeForParent($query, int $parentId)
    {
        return $query->where('parent_id', $parentId);
    }

    /**
     * Pedidos de un período.
     */
    public function scopeForPeriod($query, int $periodId)
    {
        return $query->where('period_id', $periodId);
    }

    /**
     * Pedidos no eliminados.
     */
    public function scopeNotDeleted($query)
    {
        return $query->where('deleted', 0);
    }
}