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/StudentService.php
<?php

namespace App\Services;

use App\Http\Resources\StudentResource;
use App\Repositories\ParentRepository;
use App\Repositories\StudentRepository;
use App\Repositories\PeriodRepository;
use App\Repositories\ContractRepository;
use App\Services\ApiInboundService;
use App\Services\DebtCalculationService;
use App\Models\StudentPeriod;
use App\Models\Contract;
use App\Models\StatusContract;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Exception;

class StudentService
{
    protected $studentRepository;

    public function __construct(
        StudentRepository $studentRepository
        )
    {
        $this->studentRepository = $studentRepository;
    }

    public function list($parentId = null)
    {
        $registers = $this->studentRepository->getAll($parentId);
        if ($registers->count() < 1) {
            return [];
        }

        return $registers->map(function ($register) {
            return new StudentResource($register, true);
        });
    }

    public function store($request)
    {
        $isPassport = ($request->document_type ?? 'RUT') === 'PASSPORT';
        $rut = $isPassport ? strUpper($request->rut) : formatterRut($request->rut, false);

        $validateField = $this->studentRepository->getByRut($rut);
        if ($validateField) {
            $docLabel = $isPassport ? 'Pasaporte' : 'Rut';
            throw new Exception("$docLabel de estudiante ya existe en la base de datos", 409);
        }

        DB::beginTransaction();

        try {

            // Fase 1: Crear al estudiante
            $student = $this->studentRepository->create((object)$request);
            if (!$student) {
                throw new Exception("Ocurrió un problema al registrar al estudiante", 500);
            }

            // Determinar periodo: el seleccionado en el formulario, o el activo
            $periodRepository = new PeriodRepository();
            $period = !empty($request->period)
                ? $periodRepository->getById($request->period)
                : $periodRepository->getActive();

            if (!$period) {
                throw new Exception("No existe un periodo activo configurado para asignar la matrícula", 400);
            }

            StudentPeriod::firstOrCreate([
                'student_id' => $student->id,
                'period_id' => $period->id,
            ]);

            // Fase 2: Agrupar por apoderado y crear contratos (como v1 API)
            $contractRepo = app(ContractRepository::class);
            $debtService = app(DebtCalculationService::class);

            $parentId = $student->financial_parent_id;
            $periodId = $period->id;
            $parent = \App\Models\Parents::find($parentId);
            if ($parent) {
                // Determinar si es Playgroup o Regular
                $pgCourseIds = [ApiInboundService::PG_COURSE_ID, ApiInboundService::JI_COURSE_ID];
                $isPlayGroup = in_array($student->course_id, $pgCourseIds);
                $enrollmentType = $isPlayGroup ? 'playgroup' : 'regular';

                $inCourseStatusId = $contractRepo->getStatus('in_course', 'contract');
                // A. Buscar si el apoderado ya tiene un contrato de este tipo este año
                $existingContract = Contract::where('financial_parent_id', $parentId)
                    ->where('period_id', $periodId)
                    ->where('enrollment_type', $enrollmentType)
                    ->where('status_contract_id', $inCourseStatusId)
                    ->first();
                // B. Si no existe, crear el contrato nuevo
                if (!$existingContract) {
                    $periodYear = $period->period_year ?? date('Y');
                    $apiService = app(ApiInboundService::class);
                    $contractCode = $apiService->generateContractCode($periodYear, $parentId, $periodId);
                    $isPassportParent = ($parent->document_type ?? 'RUT') === 'PASSPORT';
                    $signatureStatus = $isPlayGroup ? 'not_required' : ($isPassportParent ? 'pending_manual' : 'pending');
                    $existingContract = $contractRepo->findOrcreate($contractCode, $parent, $periodId);
                    $existingContract->enrollment_type = $enrollmentType;
                    $existingContract->status_contract_id = $inCourseStatusId;
                    $existingContract->status_payment_id = $contractRepo->getStatus('pending', 'payment');
                    $existingContract->status_signature_id = $contractRepo->getStatus($signatureStatus, 'signature');
                    $existingContract->observation = $isPlayGroup
                        ? 'Contrato Play Group generado desde formulario web.'
                        : 'Contrato generado desde formulario web.';
                    $existingContract->save();

                    Log::info("Contrato {$existingContract->code_contract} creado para apoderado {$parentId}");
                }
                // C. Generar deudas para este alumno
                // Leemos si paga incorporación directamente del request
                $paysIncFee = isset($request->pays_incorporation_fee) ? (bool)$request->pays_incorporation_fee : true;

                $debts = $debtService->calculateStudentDebts($student->id, $periodId, $existingContract->id, null, $paysIncFee);
                Log::info("Estudiante {$student->id} agregado a contrato con " . count($debts) . " deudas");

                try {
                    $debtService->recalculateFamilySiblingDebts($parentId, $periodId);
                }
                catch (\Exception $e) {
                    Log::warning("Error al recalcular hermanos: " . $e->getMessage());
                    throw new Exception("Error al recalcular hermanos: " . $e->getMessage());
                }
            }
            DB::commit();
            return $student;
        }
        catch (\Exception $e) {
            DB::rollBack();
            Log::warning("Error al crear estudiante: " . $e->getMessage());
            throw $e;
        }
    }



    public function update($request, $id)
    {
        $register = $this->show($id, true, true);

        $isPassport = ($request->document_type ?? 'RUT') === 'PASSPORT';
        $rut = $isPassport ? trim($request->rut) : formatterRut($request->rut, false);
        $validateField = $this->studentRepository->getByRut($rut, $id);
        if ($validateField) {
            $docLabel = $isPassport ? 'Pasaporte' : 'Rut';
            throw new Exception("$docLabel de estudiante ya existe en la base de datos", 409);
        }


        $update = $this->studentRepository->update($register, $request);
        if (!$update) {
            throw new Exception("Ocurrió un problema al actualizar registro", 500);
        }
        return true;
    }

    public function show($id, $allData = true, $allDataEdit = false)
    {
        $register = $this->studentRepository->getById($id);

        if (!$register) {
            throw new Exception("Estudiante no existe o fue eliminado", 400);
        }

        if ($allDataEdit) {
            return $register;
        }
        return (object)(new StudentResource($register, $allData))->resolve();
    }

    /**
     * createOrUpdate masivo para API v1
     */
    public function createOrUpdateBatch(array $students, $periodId = null)
    {
        $parentRepository = new ParentRepository();
        $results = [];

        foreach ($students as $studentData) {
            try {
                // Resolve parent by RUT
                $parentRutValue = $studentData['parent_rut'] ?? null;
                if (!$parentRutValue) {
                    throw new Exception("parent_rut es requerido");
                }

                $parentRut = strUpper(formatterRut($parentRutValue, false));
                $parent = $parentRepository->getByRut($parentRut);
                // Fallback: buscar sin importar mayúsculas/minúsculas
                if (!$parent) {
                    $parent = \App\Models\Parents::whereRaw('UPPER(rut) = ?', [$parentRut])
                        ->where('deleted', false)->first();
                }
                if (!$parent) {
                    throw new Exception("Apoderado con RUT {$parentRutValue} no encontrado en el sistema");
                }

                // Resolver document_type: si viene student_passport, es PASSPORT
                $isPassport = ($studentData['document_type'] ?? 'RUT') === 'PASSPORT';

                $result = $this->studentRepository->createOrUpdateByRut(
                    (object)$studentData,
                    $parent->id,
                    $isPassport
                );

                $results[] = [
                    'success' => true,
                    'id' => $result['student']->id,
                    'key' => $studentData['student_rut'],
                    'operation' => $result['operation'],
                    'parent_id' => $parent->id,
                    'course_id' => $result['student']->course_id,
                    'errors' => '',
                ];
            }
            catch (Exception $e) {
                $results[] = [
                    'success' => false,
                    'id' => null,
                    'key' => $studentData['student_rut'] ?? '',
                    'operation' => 'error',
                    'parent_id' => null,
                    'course_id' => null,
                    'errors' => $e->getMessage(),
                ];
            }
        }

        return $results;
    }

    /**
     * Obtener estudiante por RUT (API v1)
     */
    public function getByRut($rut)
    {
        $register = $this->studentRepository->getByRutGlobal($rut);

        if (!$register) {
            throw new Exception("Estudiante no encontrado", 404);
        }

        return (object)(new StudentResource($register, true))->resolve();
    }

    public function delete($id)
    {
        $register = $this->show($id, true, true);
        $register->deleted = 1;
        $register->deleted_at = now();
        $register->user_deleted = auth()->user()->id;
        if (!$register->save()) {
            throw new Exception("Ocurrió un problema al eliminar registro", 500);
        }
        return true;
    }
}