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

namespace App\Services\ExcelProcessors;

abstract class BaseExcelProcessor
{
    /**
     * Procesar una línea del Excel y validarla
     * 
     * @param array $data
     * @param int $lineNumber
     * @return array ['status' => 'SUCCESS|ERROR|WARNING', 'error' => '', 'warning' => '']
     */
    abstract public function processLine(array $data, int $lineNumber): array;

    /**
     * Crear o actualizar el registro en la base de datos
     * 
     * @param array $data
     * @return array ['action' => 'created|updated', 'model' => Model]
     */
    abstract public function createOrUpdateRecord(array $data): array;


    /**
     * Orden de columnas para mostrar en la tabla del frontend.
     * Cada procesador puede sobreescribir este método para definir su propio orden.
     * Si retorna null, se usa el orden original del Excel (Object.keys del JSON).
     */
    public function getColumnOrder(): ?array
    {
        return null;
    }

    /**
     * Validar fecha
     */
    protected function parseFlexibleDate(string $value): ?string
    {
        try {
            $value = trim($value);

            // 1️⃣ Si viene como número serial de Excel
            if (is_numeric($value)) {
                return \PhpOffice\PhpSpreadsheet\Shared\Date::excelToDateTimeObject($value)->format('Y-m-d');
            }

            // 2️⃣ Si viene con guiones o barras
            $formats = ['d-m-Y', 'Y-m-d', 'd/m/Y', 'Y/m/d', 'm-d-Y', 'm/d/Y'];
            foreach ($formats as $format) {
                $dt = \DateTime::createFromFormat($format, $value);
                if ($dt && $dt->format($format) === $value) {
                    return $dt->format('Y-m-d');
                }
            }

            // 3️⃣ Si Laravel puede parsearlo automáticamente
            $dt = \Carbon\Carbon::parse($value);
            return $dt ? $dt->format('Y-m-d') : null;
        } catch (\Exception $e) {
            return null;
        }
    }


    /**
     * Obtener valor del array con clave insensible a mayúsculas
     */
    public function getValue(array $data, string $key, $default = null)
    {
        // Buscar la clave exacta
        if (isset($data[$key])) {
            return $data[$key];
        }

        // Buscar insensible a mayúsculas
        $lowerKey = strtolower($key);
        foreach ($data as $k => $v) {
            if (strtolower($k) === $lowerKey) {
                return $v;
            }
        }

        // Buscar con clave normalizada (sin caracteres especiales como ¿, ?, !, etc.)
        $normalizedKey = preg_replace('/[^a-z0-9_]/', '', $lowerKey);
        foreach ($data as $k => $v) {
            $normalizedK = preg_replace('/[^a-z0-9_]/', '', strtolower($k));
            if ($normalizedK === $normalizedKey) {
                return $v;
            }
        }

        return $default;
    }

    /**
     * Generate a unique contract code with global sequential correlative.
     * Format: CTR-YYYY-NNNN where NNNN is the next available number for that year.
     */
    protected function generateContractCode($periodYear)
    {
        $prefix = sprintf('CTR-%s-', $periodYear);

        $lastCode = \App\Models\Contract::where('code_contract', 'like', $prefix . '%')
            ->whereRaw("code_contract REGEXP ?", ['^CTR-' . $periodYear . '-[0-9]+$'])
            ->orderByRaw("CAST(SUBSTRING(code_contract, ?) AS UNSIGNED) DESC", [strlen($prefix) + 1])
            ->value('code_contract');

        $nextNumber = 1;
        if ($lastCode) {
            $numericPart = substr($lastCode, strlen($prefix));
            $parsed = intval($numericPart);
            if ($parsed > 0) {
                $nextNumber = $parsed + 1;
            }
        }

        return sprintf('CTR-%s-%04d', $periodYear, $nextNumber);
    }
}