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_matriculas/app/Services/DashboardService.php
<?php

namespace App\Services;

use App\Models\ContractDetail;
use App\Repositories\ContractRepository;
use App\Repositories\ProfileRepository;
use App\Repositories\StudentRepository;
use App\Repositories\UserRepository;
use App\Repositories\WebhookRepository;
use App\Models\PresencialPayment;
use Illuminate\Support\Facades\Log;
use Carbon\Carbon;

class DashboardService
{

    protected $periodService;
    protected $postulationService;
    protected $contractService;
    protected $studentService;

    public function __construct(
        PeriodService   $periodService,
        PostulationService   $postulationService,
        ContractService   $contractService,
        StudentService   $studentService

    ) {
        $this->periodService = $periodService;
        $this->postulationService = $postulationService;
        $this->contractService = $contractService;
        $this->studentService = $studentService;
    }

    public function getData($periodId = null)
    {
        $data = [];
        $students = new StudentRepository();

        // Obtener período: si se pasa un ID usar ese, si no el activo
        if ($periodId) {
            $currentPeriod = $this->periodService->getPeriodDataById($periodId);
        } else {
            $currentPeriod = $this->periodService->getPeriodActivate();
        }

        if (auth()->check() && auth()->user()->profile->code === 'parent') {
            $data['postulations'] = !empty($currentPeriod) ? $this->postulationService->list($currentPeriod['period_year'], null) : [];

            // Obtener contrato del parent para el período seleccionado
            if ($periodId) {
                $contractRepo = new ContractRepository();
                $register = $contractRepo->getByPeriodId($periodId);
                if ($register) {
                    $data['contract'] = [
                        'code' => $register->code_contract,
                        'year' => $register->period->period_year,
                        'status_contract' => $register->statusContract->code,
                        'paid' => $register->statusPayment->code == 'completed',
                        'paid_at' => !empty($register->paid_at) ? sortDateHourForHuman($register->paid_at) : sortDateHourForHuman($register->updated_at),
                        'signed' => in_array($register->statusSignature->code, ['signed', 'manual_signed']),
                        'is_manual_signature' => in_array($register->statusSignature->code, ['pending_manual', 'manual_signed']),
                        'total_amount' => $register->total_amount,
                        'enrollment_type' => $register->enrollment_type,
                    ];
                } else {
                    $data['contract'] = null;
                }
            } else {
                $data['contract'] = $this->contractService->getContractActive();
            }

            $allStudents = $students->getAll();

            // CAE/CFE: alumnos con blocked_student (tienen prioridad sobre INACTIVE).
            $blockedStudents = $allStudents->whereNotNull('blocked_student')
                ->map(function ($student) {
                    return [
                        'id' => $student['id'],
                        'rut' => $student['rut'],
                        'full_name' => "{$student['first_name']} {$student['last_name']}",
                        'blocked_student' => $student['blocked_student'],
                    ];
                });

            // INACTIVE: alumnos con status=0 que NO estén ya en CAE/CFE.
            $inactiveStudents = $allStudents
                ->where('status', 0)
                ->whereNull('blocked_student')
                ->map(function ($student) {
                    return [
                        'id' => $student['id'],
                        'rut' => $student['rut'],
                        'full_name' => "{$student['first_name']} {$student['last_name']}",
                        'blocked_student' => 'INACTIVE',
                    ];
                });

            $grouped = $blockedStudents->groupBy('blocked_student');
            if ($inactiveStudents->isNotEmpty()) {
                $grouped['INACTIVE'] = $inactiveStudents->values();
            }

            $data['students'] = $grouped;
            $configuration = getConfiguration('general');
            $yearReplace = $currentPeriod['period_year'] ?? '';
            $data['configuration'] = [
                'whatsapp' => $configuration['whatsapp'],
                'email' => $configuration['email'],
                'cae_title' => str_replace('#PERIOD_YEAR', $yearReplace, $configuration['cae_title']),
                'cae_message' => $configuration['cae_message'],
                'cfe_title' => str_replace('#PERIOD_YEAR', $yearReplace, $configuration['cfe_title']),
                'cfe_message' => $configuration['cfe_message'],
                'inactive_title' => str_replace('#PERIOD_YEAR', $yearReplace, $configuration['inactive_title'] ?? 'Matrícula no disponible para #PERIOD_YEAR'),
                'inactive_message' => $configuration['inactive_message'] ?? 'Tu alumno se encuentra inactivo en el sistema. Para reactivarlo o resolver dudas, contacta a administración.',
                'enrollment_start_date' => $configuration['enrollment_start_date'] ?? null,
                'enrollment_end_date' => $configuration['enrollment_end_date'] ?? null,
            ];
        } else {
            $contracts = new ContractRepository();
            $users = new UserRepository();
            $profile = new ProfileRepository();
            $webhook = new WebhookRepository();
            $profileParent = $profile->getByCode('parent');
            $data['postulations'] = !empty($currentPeriod) ? $this->postulationService->list($currentPeriod['period_year']) : [];

            // KPIs derivados de contratos del período
            $activePeriodId = !empty($currentPeriod) ? ($currentPeriod['id'] ?? null) : null;
            $contractsInPeriod = $contracts->getAll($activePeriodId);
            $contractIds = $contractsInPeriod->pluck('id');

            // Estudiantes: contar desde contracts_detail (student_id vive en el detalle)
            $studentsCount = ContractDetail::whereIn('contract_id', $contractIds)
                ->whereNotNull('student_id')
                ->distinct('student_id')
                ->count('student_id');

            $data['kpi'] = [
                'students' => $studentsCount,
                'contracts' => $contractsInPeriod->count(),
                'parents' => $contractsInPeriod->pluck('financial_parent_id')->filter()->unique()->count(),
                'users' => $profileParent ? $users->getAll($profileParent->id, '!=')->count() : $users->getAll()->count(),
            ];
            // Status summary: contract counts grouped by status_contract code
            $data['status_summary'] = $contractsInPeriod
                ->groupBy(fn($c) => $c->statusContract->code ?? 'unknown')
                ->map(fn($group, $code) => [
                    'code'  => $code,
                    'label' => $group->first()->statusContract->description ?? $code,
                    'count' => $group->count(),
                ])
                ->values();

            // Recent payments: last 10 presencial payments for contracts in this period
            $data['recent_payments'] = PresencialPayment::whereIn('contract_id', $contractIds)
                ->with(['contract', 'paymentMethodRecord', 'registeredByUser'])
                ->orderBy('created_at', 'desc')
                ->limit(10)
                ->get()
                ->map(function ($p) {
                    // Currency: only TOKU (subscription) payments store amount in original currency (UF)
                    // Presencial payments always store amount in CLP regardless of concept currency
                    $currency = '$';
                    if ($p->subscription_status) {
                        $details = $p->receipt_data['details'] ?? [];
                        $currencies = array_unique(array_column($details, 'currency'));
                        $currency = count($currencies) === 1 ? $currencies[0] : '$';
                    }

                    return [
                        'id'                    => $p->id,
                        'created_at'            => $p->created_at->format('d/m/Y H:i'),
                        'amount'                => $p->amount,
                        'currency'              => $currency,
                        'reconciliation_status' => $p->reconciliation_status,
                        'subscription_status'   => $p->subscription_status,
                        'payment_method'        => $p->paymentMethodRecord->payment_method ?? ($p->subscription_status ? 'TOKU' : 'N/A'),
                        'contract_code'         => $p->contract->code_contract ?? null,
                        'parent_name'           => trim(($p->contract->financial_parent_first_name ?? '') . ' ' . ($p->contract->financial_parent_last_name ?? '')),
                    ];
                });

            // Alerts: counts of actionable pending items
            $pendingPayment = $contractsInPeriod->filter(fn($c) =>
                in_array($c->statusPayment->code ?? '', ['pending', 'partial'])
            )->count();

            $pendingSignature = $contractsInPeriod->filter(fn($c) =>
                in_array($c->statusSignature->code ?? '', ['pending', 'sent', 'pending_manual'])
            )->count();

            $incompleteData = $contractsInPeriod->filter(fn($c) =>
                in_array($c->statusContract->code ?? '', ['in_course', 'pending_payment', 'pending_signature'])
                && (empty($c->financial_parent_email) || empty($c->financial_parent_rut))
            )->count();

            $data['alerts'] = [
                ['key' => 'pending_payment',   'count' => $pendingPayment,   'label' => 'pendientes de pago'],
                ['key' => 'pending_signature', 'count' => $pendingSignature, 'label' => 'pendientes de firma'],
                ['key' => 'incomplete_data',   'count' => $incompleteData,   'label' => 'con datos incompletos'],
            ];

            // Signature stats: requires_signature (non-playgroup) vs playgroup
            $requiresSignature = $contractsInPeriod->filter(fn($c) => ($c->enrollment_type ?? '') !== 'playgroup');
            $playgroup = $contractsInPeriod->filter(fn($c) => ($c->enrollment_type ?? '') === 'playgroup');
            $signedElectronic = $requiresSignature->filter(fn($c) => ($c->statusSignature->code ?? '') === 'signed')->count();
            $signedManual = $requiresSignature->filter(fn($c) => ($c->statusSignature->code ?? '') === 'manual_signed')->count();
            $signed = $signedElectronic + $signedManual;
            $pendingManual = $requiresSignature->filter(fn($c) => ($c->statusSignature->code ?? '') === 'pending_manual')->count();
            $data['signature_stats'] = [
                'requires_signature' => $requiresSignature->count(),
                'signed'             => $signed,
                'signed_electronic'  => $signedElectronic,
                'signed_manual'      => $signedManual,
                'pending'            => $requiresSignature->count() - $signed - $pendingManual,
                'pending_manual'     => $pendingManual,
                'playgroup'          => $playgroup->count(),
            ];
        }

        $data['period'] = $currentPeriod;

        // Añadir información del segmento activo
        if (!empty($currentPeriod) && isset($currentPeriod['segments'])) {
            $today = Carbon::today();
            $activeSegment = null;

            foreach ($currentPeriod['segments'] as $segment) {
                if ($segment['status'] == 1) {
                    $startDate = Carbon::parse($segment['start_date']);
                    $endDate = Carbon::parse($segment['end_date']);

                    if ($today->between($startDate, $endDate)) {
                        $activeSegment = $segment;
                        break;
                    }
                }
            }

            $data['active_segment'] = [
                'is_active' => !is_null($activeSegment),
                'segment' => $activeSegment
            ];
        } else {
            $data['active_segment'] = [
                'is_active' => false,
                'segment' => null
            ];
        }

        return $data;
    }
}