<?php
/**
 * motor_fap.php — Motor de Facilidades de Pago (FAP) · RND 102500000019
 *
 * Reglas (RND 102500000019):
 *  - Pago inicial (Art.8) y garantía (Arts.15-17) según el TOTAL consolidado.
 *  - Cuota (Art.9): base = saldo/N, saldo = deuda − pago inicial − garantía;
 *    cada cuota se ACTUALIZA a su fecha de pago con MV + interés Art.47
 *    (Art.7.I.a / Art.9.II). => cuota_k = base · DA(fecha_k)/DA(solicitud),
 *    donde DA(f) = deuda actualizada a la fecha f (motor_siat.php).
 *  - Cuota mínima 200 UFV; máx. 30 meses; cuotas vencen el último día hábil
 *    del mes (1ª: mes siguiente a la notificación).
 *  - UFV de cuotas futuras: proyectada por tendencia (configurable: flat|trend).
 *
 * Validado contra el caso real FAP 252534005904 (Jean Paul Malky).
 */
require_once __DIR__ . '/motor_siat.php';
require_once __DIR__ . '/vencimientos.php';

class MotorFAP {
    const UFV_API = 'https://nitro.com.de/ufv/api.php';
    const PUB_LEY1733 = '2026-05-27'; // fecha de publicación de la Ley 1733 (Art. 8.IV RND 102600000019)
    public $motor;
    private $ufvCache = [];
    private $proy = null; // [lastDate, lastUFV, dailyGrowth]

    public function __construct() { $this->motor = new MotorDeudaSIN(); }

    /** UFV de una fecha SOLO desde ufv_api (sin fallback BCB, para no bloquear). */
    private function apiUFV($fecha) {
        $ch = curl_init(self::UFV_API.'?action=get_rate&date='.urlencode($fecha));
        curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER=>true, CURLOPT_SSL_VERIFYPEER=>false,
            CURLOPT_TIMEOUT=>8, CURLOPT_CONNECTTIMEOUT=>4]);
        $r = curl_exec($ch); curl_close($ch);
        $d = json_decode($r, true);
        if (is_array($d) && isset($d['error']) && $d['error']===false && isset($d['rate'])) {
            $v = round(floatval($d['rate']),5); if ($v>1 && $v<10) return $v;
        }
        return null;
    }

    /** %s de pago inicial y garantía efectivo/valores según total consolidado. */
    public static function porcentajes($total) {
        // RND 102600000020 (08/06/2026): pago inicial 5% y garantía 5%, PLANOS
        // (sin tramos), modificando los Arts. 8, 15, 16 y 17 de la RND 102500000019.
        return ['pi'=>0.05, 'gar'=>0.05];
    }

    /**
     * Clasificación de una deuda bajo la Ley 1733 (alivio tributario):
     *  - 'condonado'    : períodos anteriores a 2018 (con tope) + TODA la gestión 2020.
     *  - 'regularizable': el resto (2018, 2019, 2021 en adelante) → TO + 50% MV, sin interés ni multa.
     */
    public static function clasificarAlivio($periodo, $venc) {
        $y = null;
        if (preg_match('/(\d{4})/', (string)$periodo, $mm)) $y = (int)$mm[1];
        if ($y === null && $venc) $y = (int)date('Y', strtotime($venc));
        if ($y === null) return 'regularizable';
        if ($y <= 2017 || $y === 2020) return 'condonado';
        return 'regularizable';
    }

    /** UFV real (ufv_api) si está disponible; si no, proyectada (Res.116/2001). */
    public function ufv($fecha) {
        if (isset($this->ufvCache[$fecha])) return $this->ufvCache[$fecha];
        if ($this->proy === null) $this->calibrarProyeccion();
        // Fechas posteriores al último dato conocido: proyectar sin consultar la API.
        if ($this->proy && strtotime($fecha) > strtotime($this->proy['lastDate'])) {
            return $this->ufvCache[$fecha] = $this->proyectar($fecha);
        }
        $v = $this->apiUFV($fecha);
        if ($v !== null) return $this->ufvCache[$fecha] = $v;
        if ($this->proy) return $this->ufvCache[$fecha] = $this->proyectar($fecha);
        return null;
    }

    /**
     * Proyecta la UFV con la metodología del BCB (Res. de Directorio 116/2001):
     *   UFV_d = UFV_(d-1) · (1 + π)^(1/n)
     * con π = inflación interanual MENSUALIZADA (raíz doceava de la anual) y
     * n = días calendario del mes. La inflación interanual se estima de la propia
     * UFV (UFV_último / UFV_hace_1_año − 1), pues la UFV ya acumula la inflación.
     */
    private function proyectar($fecha) {
        if (!$this->proy) return null;
        $ts = strtotime($this->proy['lastDate']);
        $end = strtotime($fecha);
        $ufv = $this->proy['lastUFV'];
        $pm = $this->proy['pmensual'];
        $guard = 0;
        while ($ts < $end && $guard < 4000) {
            $ts = strtotime('+1 day', $ts);
            $n = (int)date('t', $ts);              // días calendario del mes
            $ufv *= pow(1 + $pm, 1.0/$n);
            $guard++;
        }
        return round($ufv, 5);
    }

    /** Precarga en caché todas las UFV del rango con una sola llamada (get_range). */
    private function prefetch($from, $to) {
        $ch = curl_init(self::UFV_API.'?action=get_range&from='.$from.'&to='.$to);
        curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER=>true, CURLOPT_SSL_VERIFYPEER=>false,
            CURLOPT_TIMEOUT=>15, CURLOPT_CONNECTTIMEOUT=>5]);
        $r = curl_exec($ch); curl_close($ch);
        $d = json_decode($r, true);
        if (is_array($d) && !empty($d['rates'])) {
            foreach ($d['rates'] as $date=>$rate) { $v=round(floatval($rate),5); if ($v>1&&$v<10) $this->ufvCache[$date]=$v; }
        }
    }

    /** Calibra la proyección con get_latest (1 llamada) + un dato ~1 año antes. */
    private function calibrarProyeccion() {
        $ch = curl_init(self::UFV_API.'?action=get_latest');
        curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER=>true, CURLOPT_SSL_VERIFYPEER=>false,
            CURLOPT_TIMEOUT=>8, CURLOPT_CONNECTTIMEOUT=>4]);
        $r = curl_exec($ch); curl_close($ch);
        $d = json_decode($r, true);
        $last = $d['date'] ?? null; $lastV = isset($d['rate']) ? floatval($d['rate']) : null;
        if (!$last || !$lastV) { $this->proy = false; return; }
        $prevF = (new DateTime($last))->modify('-365 day')->format('Y-m-d');
        $prevV = $this->apiUFV($prevF) ?: $lastV;
        $panual = $prevV>0 ? ($lastV/$prevV - 1) : 0;      // inflación 12 meses (UFV interanual)
        $pmensual = pow(1 + $panual, 1.0/12) - 1;          // mensualizada (raíz 12) — Res.116/2001
        $this->proy = ['lastDate'=>$last, 'lastUFV'=>round($lastV,5), 'panual'=>$panual, 'pmensual'=>$pmensual];
    }

    /** Deuda actualizada (TO + MV + interés, sin sanción) a la fecha dada. */
    public function DA($to, $venc, $fecha) {
        $ufvVenc = $this->ufv($venc);
        $ufvPago = $this->ufv($fecha);
        $r = $this->motor->calcular([
            'tributoOmitido'=>$to, 'fechaVenc'=>$venc, 'fechaPago'=>$fecha,
            'ufvVenc'=>$ufvVenc, 'ufvPago'=>$ufvPago,
            'sancionOmision'=>false, 'multaDF'=>0, 'precisionFactor'=>'completa',
        ]);
        return (is_array($r) && empty($r['error'])) ? $r : null;
    }

    /**
     * Deuda regularizada en facilidades (Ley 1733, Art. 8.IV RND 102600000019):
     *  - Tributo omitido actualizado por UFV (100% del mantenimiento de valor).
     *  - Interés de FACILIDADES (Art. 47) contado SOLO desde la fecha de publicación
     *    de la Ley; el interés histórico (venc→publicación) y las multas se condonan.
     */
    public function DAreg($to, $venc, $fecha) {
        $ufvVenc = $this->ufv($venc);
        $ufvF    = $this->ufv($fecha);
        if (!$ufvVenc || !$ufvF) return null;
        $c = $to / $ufvVenc;                 // tributo en UFV
        $toActualBs = $c * $ufvF;            // TO + 100% MV a la fecha
        $intUFV = 0.0;
        if (strtotime($fecha) > strtotime(self::PUB_LEY1733)) {
            $t = $this->motor->calcularTramos(self::PUB_LEY1733, $fecha);
            foreach ($t['tramos'] as $tr) { $intUFV += pow(1 + $tr['tasa']/360, $tr['dias']) - 1; }
            $intUFV *= $c;
        }
        $intBs = $intUFV * $ufvF;
        return ['totalBs'=>round($toActualBs + $intBs, 2), 'toActualBs'=>round($toActualBs,2), 'interesBs'=>round($intBs,2)];
    }

    /** Último día hábil del mes de $ts (timestamp), retrocediendo si feriado/finde. */
    public static function ultimoHabilMes($y, $m) {
        $ts = mktime(0,0,0, $m+1, 0, $y); // último día del mes m
        $fer = Vencimientos::feriados((int)date('Y',$ts));
        $g=0;
        while (!Vencimientos::esHabil($ts, $fer)) { $ts = strtotime('-1 day',$ts); if(++$g>10) break; }
        return date('Y-m-d', $ts);
    }

    /**
     * Calcula la FAP completa.
     * @param array $in {
     *   fechaSolicitud: 'Y-m-d',
     *   mesPrimeraCuota?: int (desfase de meses desde solicitud a 1ª cuota; def 2),
     *   deudas: [ {impuesto, periodo, fechaVenc?, tributoOmitido, cuotas, multaDF?} ],
     *   tipoGarantia?: 'efectivo'|'valores'|'sin'
     * }
     */
    public function calcular($in) {
        $sol = $in['fechaSolicitud'] ?? null;
        $deudas = $in['deudas'] ?? [];
        if (!$sol || !$deudas) return ['error'=>'Faltan datos: fecha de solicitud y al menos una deuda.'];
        $offset = isset($in['mesPrimeraCuota']) ? (int)$in['mesPrimeraCuota'] : 2;
        $modo = (($in['modo'] ?? 'fap') === 'regularizacion') ? 'regularizacion' : 'fap';
        $topeCuotas = ($modo === 'regularizacion') ? 36 : 30; // Art. 8.a RND 102600000019

        // UFV de la fecha de solicitud (cuota mínima y actualización).
        if ($this->proy === null) $this->calibrarProyeccion();
        $ufvSol = $this->ufv($sol);
        $cuotaMinUFV = 200.0;

        // 1) Accesorios + total por deuda.
        $det = [];
        $totalConsolidado = 0;
        foreach ($deudas as $d) {
            $to = floatval($d['tributoOmitido'] ?? 0);
            $venc = $d['fechaVenc'] ?? null;
            if (!$venc) return ['error'=>"Falta fecha de vencimiento de una deuda ({$d['impuesto']} {$d['periodo']})."];
            $daSol = $this->DA($to, $venc, $sol);
            if (!$daSol) return ['error'=>"No se pudo calcular accesorios de {$d['impuesto']} {$d['periodo']} (UFV no disponible)."];
            $totalNormal = $daSol['totalBs'];               // TO + MV + interés (deuda hoy, sin beneficio)
            $tratamiento = 'normal'; $ahorro = 0.0; $totalPlan = $totalNormal;
            $baseReg = $totalNormal; $contado = $totalNormal;
            if ($modo === 'regularizacion') {
                $tratamiento = self::clasificarAlivio($d['periodo'] ?? '', $venc);
                if ($tratamiento === 'condonado') {
                    $ahorro = round($totalNormal, 2); $totalPlan = 0.0; $baseReg = 0.0; $contado = 0.0;
                } else {
                    // Facilidades (Art. 8.IV): TO + 100% MV actualizado a la PUBLICACIÓN de la Ley.
                    $daReg = $this->DAreg($to, $venc, self::PUB_LEY1733);
                    $baseReg = $daReg ? $daReg['totalBs'] : round($daSol['toActualizadoBs'], 2);
                    // Contado (Art. 7): TO + 50% MV (a la fecha de pago; referencia: solicitud).
                    $contado = round($daSol['toActualizadoBs'] - 0.5 * $daSol['mvBs'], 2);
                    $totalPlan = $baseReg;                          // lo que entra al plan en cuotas
                    $ahorro = round($totalNormal - $baseReg, 2);    // intereses históricos (+ multas) condonados
                }
            }
            $det[] = [
                'impuesto'=>$d['impuesto'] ?? '', 'periodo'=>$d['periodo'] ?? '',
                'to'=>$to, 'venc'=>$venc, 'cuotas'=>(int)($d['cuotas'] ?? 1),
                'daSol'=>$daSol, 'tratamiento'=>$tratamiento,
                'totalNormal'=>round($totalNormal,2), 'total'=>round($totalPlan,2),
                'baseReg'=>round($baseReg,2), 'contado'=>round($contado,2), 'ahorro'=>$ahorro,
                'mvSol'=>$daSol['mvBs'], 'intSol'=>$daSol['interesBs'],
            ];
            $totalConsolidado += $totalPlan;
        }
        $totalConsolidado = round($totalConsolidado, 2);

        // 2) Porcentajes y montos iniciales (sobre el total que entra al plan).
        $pct = self::porcentajes($totalConsolidado);
        // Garantía en efectivo/valores se descuenta del saldo; hipotecaria/primer
        // requerimiento ('sin') no se descuenta (es un respaldo, no pago).
        $descuentaGar = in_array(($in['tipoGarantia'] ?? 'efectivo'), ['efectivo','valores'], true); // hipotecaria/primer requerim./sin: no se paga garantía en efectivo
        $garPct = $descuentaGar ? $pct['gar'] : 0.0;
        $pagoInicialTotal = round($totalConsolidado * $pct['pi'], 2);
        $garantiaTotal    = round($totalConsolidado * $garPct, 2);

        // 3) Por deuda: pago inicial/garantía proporcional, saldo, tope dinámico de
        //    cuotas (cada cuota ≥ 200 UFV → N ≤ saldo_UFV/200; tope legal 30) y base.
        foreach ($det as &$x) {
            if ($x['total'] <= 0) { // condonado: no entra al plan
                $x['pagoInicial']=0; $x['garantia']=0; $x['saldo']=0; $x['base']=0;
                $x['maxCuotasDeuda']=0; $x['cuotas']=0; continue;
            }
            $x['pagoInicial'] = round($x['total'] * $pct['pi'], 2);
            $x['garantia']    = round($x['total'] * $garPct, 2);
            $x['saldo']       = round($x['total'] - $x['pagoInicial'] - $x['garantia'], 2);
            $maxNd = $topeCuotas;
            if ($ufvSol && $ufvSol>0) { $maxNd = max(1, min($topeCuotas, (int)floor(($x['saldo']/$ufvSol)/$cuotaMinUFV))); }
            $x['maxCuotasDeuda'] = $maxNd;
            if ($x['cuotas'] > $maxNd) { $x['cuotasAjustada']=true; $x['cuotasOriginal']=$x['cuotas']; $x['cuotas']=$maxNd; }
            if ($x['cuotas'] < 1) $x['cuotas'] = 1;
            $x['base'] = $x['saldo'] / $x['cuotas'];
        }
        unset($x);
        $saldoTotal = round(array_sum(array_column($det,'saldo')), 2);

        // 4) Cronograma de fechas (último día hábil del mes). Solo deudas activas.
        $activos = array_values(array_filter($det, function($x){ return $x['total']>0; }));
        $maxN = $activos ? min(max(array_column($activos,'cuotas')), $topeCuotas) : 0;
        $cronograma = [];
        $dt = new DateTime($sol);

        $cuotaMinBs = $ufvSol ? round($cuotaMinUFV * $ufvSol, 2) : null;
        $saldoUFV = ($ufvSol && $ufvSol>0) ? $saldoTotal / $ufvSol : null;
        $maxCuotasPermitidas = $saldoUFV ? min(30, max(1, (int)floor($saldoUFV / $cuotaMinUFV))) : 30;

        // Precarga las UFV reales del rango en una sola llamada (get_range).
        $fromTs = strtotime($sol);
        foreach ($det as $x) { $t = strtotime($x['venc']); if ($t < $fromTs) $fromTs = $t; }
        $toReal = ($this->proy ? $this->proy['lastDate'] : date('Y-m-d'));
        $this->prefetch(date('Y-m-d',$fromTs), $toReal);

        for ($k=1; $k<=$maxN; $k++) {
            $mc = (clone $dt)->modify('first day of this month')->modify('+'.($offset + $k - 1).' month');
            $fecha = self::ultimoHabilMes((int)$mc->format('Y'), (int)$mc->format('n'));
            $ufvF = $this->ufv($fecha);

            $filas = []; $totCuota = 0; $compTO = 0; $compMV = 0; $compInt = 0;
            foreach ($det as $x) {
                if ($x['total'] <= 0 || $k > $x['cuotas']) continue;
                if ($modo === 'regularizacion') {
                    // Facilidades (Art. 8.IV): cuota = base · DAreg(fecha)/DAreg(publicación);
                    // crece por MV (publicación→pago) + interés de facilidades (Art. 47 desde la publicación).
                    $daF = $this->DAreg($x['to'], $x['venc'], $fecha);
                    if (!$daF || $x['baseReg'] <= 0) continue;
                    $cuota = round($x['base'] * $daF['totalBs'] / $x['baseReg'], 2);
                    $pInt = $daF['totalBs']>0 ? $daF['interesBs']/$daF['totalBs'] : 0;
                    $montoSinInt = $cuota * (1 - $pInt);
                    $cTO = round($x['base'], 2);
                    $cMV = round($montoSinInt - $x['base'], 2);
                    $cInt = round($cuota - $montoSinInt, 2);
                } else {
                    $da = $this->DA($x['to'], $x['venc'], $fecha);
                    if (!$da) continue;
                    $cuota = round($x['base'] * $da['totalBs'] / $x['daSol']['totalBs'], 2);
                    $tot = $da['totalBs'] ?: 1;
                    $pMV = $da['mvBs'] / $tot;
                    $pToPuro = ($da['toActualizadoBs'] - $da['mvBs']) / $tot;
                    $pInt = $da['interesBs'] / $tot;
                    $cTO = round($cuota * $pToPuro, 2); $cMV = round($cuota * $pMV, 2); $cInt = round($cuota * $pInt, 2);
                }
                $filas[] = ['impuesto'=>$x['impuesto'], 'periodo'=>$x['periodo'],
                    'cuota'=>$cuota, 'to'=>$cTO, 'mv'=>$cMV, 'interes'=>$cInt];
                $totCuota += $cuota; $compTO += $cTO; $compMV += $cMV; $compInt += $cInt;
            }
            $minBsF = $ufvF ? round($cuotaMinUFV * $ufvF, 2) : null;
            $cronograma[] = [
                'n'=>$k, 'fecha'=>$fecha, 'ufv'=>$ufvF,
                'proyectada'=>($this->proy && strtotime($fecha) > strtotime($this->proy['lastDate'])),
                'minBs'=>$minBsF,
                'bajoMinimo'=>($minBsF !== null && round($totCuota,2) < $minBsF),
                'vencida'=>(strtotime($fecha) <= time()),
                'totalCuota'=>round($totCuota,2), 'deudasActivas'=>count($filas),
                'compTO'=>round($compTO,2), 'compMV'=>round($compMV,2), 'compInt'=>round($compInt,2),
                'detalle'=>$filas,
            ];
        }

        // Avisos de validación.
        $avisos = [];
        $nBajo = count(array_filter($cronograma, function($c){ return !empty($c['bajoMinimo']); }));
        if ($nBajo > 0) {
            $avisos[] = "Hay $nBajo cuota(s) por debajo del mínimo de 200 UFV (".
                ($cuotaMinBs!==null?('≈ Bs '.number_format($cuotaMinBs,2,',','.')):'')."). Reduzca el número de cuotas.";
        }
        foreach (array_filter($det, function($x){ return !empty($x['cuotasAjustada']); }) as $x) {
            $avisos[] = "La deuda {$x['impuesto']} {$x['periodo']} se ajustó de {$x['cuotasOriginal']} a {$x['cuotas']} cuota(s): cada cuota debe ser ≥ 200 UFV.";
        }
        if ($maxN >= $topeCuotas) $avisos[] = "El plan usa el máximo de $topeCuotas cuotas permitido.";

        // Resumen vencidas (UFV cierta) vs por venir (UFV proyectada).
        $venc = array_filter($cronograma, function($c){ return !empty($c['vencida']); });
        $porVenir = array_filter($cronograma, function($c){ return empty($c['vencida']); });
        $totalVencido  = round(array_sum(array_column($venc, 'totalCuota')), 2);
        $totalPorVenir = round(array_sum(array_column($porVenir, 'totalCuota')), 2);

        // Resumen del alivio (Ley 1733).
        $ahorroTotal = round(array_sum(array_column($det,'ahorro')), 2);
        $totalNormalSum = round(array_sum(array_column($det,'totalNormal')), 2);
        $condon = array_filter($det, function($x){ return $x['tratamiento']==='condonado'; });
        $totalCondonado = round(array_sum(array_column($condon,'totalNormal')), 2);
        $totalContado = round(array_sum(array_map(function($x){ return $x['tratamiento']==='regularizable' ? $x['contado'] : 0; }, $det)), 2);

        return [
            'error'=>false, 'modo'=>$modo,
            'fechaSolicitud'=>$sol, 'ufvSolicitud'=>$ufvSol,
            'cuotaMinimaUFV'=>$cuotaMinUFV, 'cuotaMinimaBs'=>$cuotaMinBs,
            'maxCuotasPermitidas'=>$maxCuotasPermitidas, 'topeCuotas'=>$topeCuotas,
            'pubLey'=>self::PUB_LEY1733,
            'inflacionAnualEstim'=>($this->proy ? round($this->proy['panual']*100,2) : null),
            'ufvUltimaFecha'=>($this->proy ? $this->proy['lastDate'] : null),
            'hoy'=>date('Y-m-d'),
            'nVencidas'=>count($venc), 'totalVencido'=>$totalVencido,
            'nPorVenir'=>count($porVenir), 'totalPorVenir'=>$totalPorVenir,
            'avisos'=>$avisos,
            'totalConsolidado'=>$totalConsolidado,
            'totalNormal'=>$totalNormalSum,
            'totalCondonado'=>$totalCondonado, 'ahorroTotal'=>$ahorroTotal, 'totalContado'=>$totalContado,
            'nCondonadas'=>count($condon),
            'nRegularizables'=>count(array_filter($det, function($x){ return $x['tratamiento']==='regularizable'; })),
            'porcentajePagoInicial'=>$pct['pi']*100,
            'porcentajeGarantia'=>$garPct*100,
            'pagoInicial'=>$pagoInicialTotal, 'garantia'=>$garantiaTotal,
            'saldoTotal'=>$saldoTotal, 'maxCuotas'=>$maxN,
            'deudas'=>array_map(function($x){ return [
                'impuesto'=>$x['impuesto'],'periodo'=>$x['periodo'],'venc'=>$x['venc'],
                'tratamiento'=>$x['tratamiento'],
                'tributoOmitido'=>$x['to'],'mv'=>$x['mvSol'],'interes'=>$x['intSol'],
                'mvFull'=>round($x['daSol']['mvBs'],2),'interesFull'=>round($x['daSol']['interesBs'],2),
                'totalNormal'=>$x['totalNormal'],'total'=>$x['total'],'ahorro'=>$x['ahorro'],
                'baseReg'=>$x['baseReg'],'contado'=>$x['contado'],
                'pagoInicial'=>$x['pagoInicial'],'garantia'=>$x['garantia'],
                'saldo'=>$x['saldo'],'cuotas'=>$x['cuotas'],'maxCuotasDeuda'=>$x['maxCuotasDeuda'],
            ]; }, $det),
            'cronograma'=>$cronograma,
        ];
    }
}
