<?php

namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use App\Models\Transaction;
use App\Models\Category;
use App\Models\Account;
use App\Models\CostCenter;
use App\Models\LiabilityAccount;
use App\Models\LiabilityInstallment;
use App\Models\RecurringTemplate;
use App\Models\RecurringInstance;
use App\Models\Budget;
use App\Models\FinancialGoal;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Carbon\Carbon;

class ReportController extends Controller
{
    private $userId;
    private $primaryCurrency;
    private $exchangeRates;

    private function init()
    {
        $this->userId = Auth::id();
        $this->setPrimaryCurrency();
        $this->loadExchangeRates();
    }

    private function setPrimaryCurrency()
    {
        $account = Account::where('user_id', $this->userId)
            ->where('include_in_total', true)
            ->orderByDesc('current_balance')
            ->first();
        
        $this->primaryCurrency = $account->currency ?? 'EUR';
    }

    private function loadExchangeRates()
    {
        $this->exchangeRates = [
            'EUR' => 1.0, 'USD' => 0.92, 'GBP' => 1.17, 'BRL' => 0.18,
            'MXN' => 0.054, 'COP' => 0.00023, 'ARS' => 0.0011, 'CLP' => 0.0010, 'PEN' => 0.25,
        ];
    }

    private function convertToPrimaryCurrency($amount, $fromCurrency)
    {
        if ($fromCurrency === $this->primaryCurrency) return $amount;
        $rate = $this->exchangeRates[$fromCurrency] ?? 1;
        $primaryRate = $this->exchangeRates[$this->primaryCurrency] ?? 1;
        return ($amount * $rate) / $primaryRate;
    }

    // Condición base para excluir transferencias
    private function excludeTransfers()
    {
        return "(is_transfer IS NULL OR is_transfer = 0)";
    }

    /**
     * Resumen general de reportes con multi-divisa
     */
    public function summary(Request $request)
    {
        $this->init();
        $year = $request->get('year', now()->year);
        
        // Datos por moneda
        $yearData = DB::select("
            SELECT 
                COALESCE(a.currency, 'EUR') as currency,
                SUM(CASE WHEN t.type = 'credit' THEN t.amount ELSE 0 END) as income,
                SUM(CASE WHEN t.type = 'debit' THEN ABS(t.amount) ELSE 0 END) as expense
            FROM transactions t
            LEFT JOIN accounts a ON t.account_id = a.id
            WHERE t.user_id = ? 
            AND YEAR(t.effective_date) = ?
            AND t.deleted_at IS NULL
            AND {$this->excludeTransfers()}
            GROUP BY COALESCE(a.currency, 'EUR')
        ", [$this->userId, $year]);
        
        $lastYearData = DB::select("
            SELECT 
                COALESCE(a.currency, 'EUR') as currency,
                SUM(CASE WHEN t.type = 'credit' THEN t.amount ELSE 0 END) as income,
                SUM(CASE WHEN t.type = 'debit' THEN ABS(t.amount) ELSE 0 END) as expense
            FROM transactions t
            LEFT JOIN accounts a ON t.account_id = a.id
            WHERE t.user_id = ? 
            AND YEAR(t.effective_date) = ?
            AND t.deleted_at IS NULL
            AND {$this->excludeTransfers()}
            GROUP BY COALESCE(a.currency, 'EUR')
        ", [$this->userId, $year - 1]);
        
        // Convertir y sumar
        $currentIncome = 0; $currentExpense = 0;
        $byCurrency = [];
        foreach ($yearData as $row) {
            $currentIncome += $this->convertToPrimaryCurrency($row->income, $row->currency);
            $currentExpense += $this->convertToPrimaryCurrency($row->expense, $row->currency);
            $byCurrency[$row->currency] = [
                'income' => round($row->income, 2),
                'expense' => round($row->expense, 2),
                'balance' => round($row->income - $row->expense, 2),
            ];
        }
        
        $previousIncome = 0; $previousExpense = 0;
        foreach ($lastYearData as $row) {
            $previousIncome += $this->convertToPrimaryCurrency($row->income, $row->currency);
            $previousExpense += $this->convertToPrimaryCurrency($row->expense, $row->currency);
        }
        
        return response()->json([
            'year' => $year,
            'currency' => $this->primaryCurrency,
            'current' => [
                'income' => round($currentIncome, 2),
                'expense' => round($currentExpense, 2),
                'balance' => round($currentIncome - $currentExpense, 2),
            ],
            'previous' => [
                'income' => round($previousIncome, 2),
                'expense' => round($previousExpense, 2),
                'balance' => round($previousIncome - $previousExpense, 2),
            ],
            'variation' => [
                'income' => $previousIncome > 0 ? round((($currentIncome - $previousIncome) / $previousIncome) * 100, 1) : 0,
                'expense' => $previousExpense > 0 ? round((($currentExpense - $previousExpense) / $previousExpense) * 100, 1) : 0,
            ],
            'by_currency' => $byCurrency,
        ]);
    }

    /**
     * Reporte por categorías con multi-divisa
     */
    public function byCategory(Request $request)
    {
        $this->init();
        $startDate = $request->get('start_date', now()->startOfYear()->format('Y-m-d'));
        $endDate = $request->get('end_date', now()->format('Y-m-d'));
        $type = $request->get('type', 'debit');
        $groupByParent = $request->get('group_by_parent', false);
        $parentId = $request->get('parent_id');
        
        // Se filtrar por parent_id, mostra subcategorias dessa categoria pai
        if ($parentId) {
            $data = DB::select("
                SELECT 
                    c.id as category_id,
                    c.name as category_name,
                    c.icon,
                    c.color,
                    COALESCE(a.currency, 'EUR') as currency,
                    SUM(ABS(t.amount)) as total
                FROM transactions t
                LEFT JOIN categories c ON t.category_id = c.id
                LEFT JOIN accounts a ON t.account_id = a.id
                WHERE t.user_id = ? 
                AND t.effective_date BETWEEN ? AND ?
                AND t.type = ?
                AND (c.id = ? OR c.parent_id = ?)
                AND t.deleted_at IS NULL
                AND {$this->excludeTransfers()}
                GROUP BY c.id, c.name, c.icon, c.color, COALESCE(a.currency, 'EUR')
                ORDER BY total DESC
            ", [$this->userId, $startDate, $endDate, $type, $parentId, $parentId]);
        }
        // Si se quiere agrupar por categoría padre, obtenemos el nombre del padre
        else if ($groupByParent) {
            $data = DB::select("
                SELECT 
                    COALESCE(c.parent_id, c.id) as category_id,
                    COALESCE(cp.name, c.name) as category_name,
                    COALESCE(cp.icon, c.icon) as icon,
                    COALESCE(cp.color, c.color) as color,
                    COALESCE(a.currency, 'EUR') as currency,
                    SUM(ABS(t.amount)) as total
                FROM transactions t
                LEFT JOIN categories c ON t.category_id = c.id
                LEFT JOIN categories cp ON c.parent_id = cp.id
                LEFT JOIN accounts a ON t.account_id = a.id
                WHERE t.user_id = ? 
                AND t.effective_date BETWEEN ? AND ?
                AND t.type = ?
                AND t.deleted_at IS NULL
                AND {$this->excludeTransfers()}
                GROUP BY COALESCE(c.parent_id, c.id), COALESCE(cp.name, c.name), COALESCE(cp.icon, c.icon), COALESCE(cp.color, c.color), COALESCE(a.currency, 'EUR')
                ORDER BY total DESC
            ", [$this->userId, $startDate, $endDate, $type]);
        } else {
            // Vista padrão: agrupar por categoria pai (soma transações de subcategorias)
            $data = DB::select("
                SELECT 
                    COALESCE(c.parent_id, c.id) as category_id,
                    COALESCE(cp.name, c.name) as category_name,
                    COALESCE(cp.icon, c.icon) as icon,
                    COALESCE(cp.color, c.color) as color,
                    COALESCE(a.currency, 'EUR') as currency,
                    SUM(ABS(t.amount)) as total
                FROM transactions t
                LEFT JOIN categories c ON t.category_id = c.id
                LEFT JOIN categories cp ON c.parent_id = cp.id
                LEFT JOIN accounts a ON t.account_id = a.id
                WHERE t.user_id = ? 
                AND t.effective_date BETWEEN ? AND ?
                AND t.type = ?
                AND t.deleted_at IS NULL
                AND {$this->excludeTransfers()}
                GROUP BY COALESCE(c.parent_id, c.id), COALESCE(cp.name, c.name), COALESCE(cp.icon, c.icon), COALESCE(cp.color, c.color), COALESCE(a.currency, 'EUR')
                ORDER BY total DESC
            ", [$this->userId, $startDate, $endDate, $type]);
        }
        
        // Agrupar por categoría y convertir monedas
        $byCategory = [];
        foreach ($data as $row) {
            $catId = $row->category_id ?? 0;
            if (!isset($byCategory[$catId])) {
                $byCategory[$catId] = [
                    'category_id' => $catId,
                    'category_name' => $row->category_name ?? 'Sin categoría',
                    'icon' => $row->icon ?? 'bi-tag',
                    'color' => $row->color ?? '#6b7280',
                    'total_converted' => 0,
                    'by_currency' => [],
                ];
            }
            $byCategory[$catId]['by_currency'][$row->currency] = round($row->total, 2);
            $byCategory[$catId]['total_converted'] += $this->convertToPrimaryCurrency($row->total, $row->currency);
        }
        
        // Ordenar y calcular porcentajes
        usort($byCategory, fn($a, $b) => $b['total_converted'] <=> $a['total_converted']);
        $grandTotal = array_sum(array_column($byCategory, 'total_converted'));
        
        $result = array_map(function($cat) use ($grandTotal) {
            $cat['total'] = round($cat['total_converted'], 2);
            $cat['percentage'] = $grandTotal > 0 ? round(($cat['total_converted'] / $grandTotal) * 100, 1) : 0;
            unset($cat['total_converted']);
            return $cat;
        }, $byCategory);
        
        return response()->json([
            'data' => array_values($result),
            'total' => round($grandTotal, 2),
            'currency' => $this->primaryCurrency,
            'period' => ['start' => $startDate, 'end' => $endDate],
        ]);
    }

    /**
     * Reporte por centro de costos (3 niveles: centro -> categoría -> subcategoría)
     */
    public function byCostCenter(Request $request)
    {
        $this->init();
        $startDate = $request->get('start_date', now()->startOfYear()->format('Y-m-d'));
        $endDate = $request->get('end_date', now()->format('Y-m-d'));
        $costCenterId = $request->get('cost_center_id');
        $categoryId = $request->get('category_id');
        
        // Nível 3: Subcategorias de uma categoria específica de um centro de custo
        if ($costCenterId !== null && $categoryId !== null) {
            $data = DB::select("
                SELECT 
                    c.id as category_id,
                    c.name as category_name,
                    c.icon as category_icon,
                    c.color as category_color,
                    COALESCE(a.currency, 'EUR') as currency,
                    SUM(CASE WHEN t.type = 'credit' THEN t.amount ELSE 0 END) as income,
                    SUM(CASE WHEN t.type = 'debit' THEN ABS(t.amount) ELSE 0 END) as expense
                FROM transactions t
                LEFT JOIN categories c ON t.category_id = c.id
                LEFT JOIN accounts a ON t.account_id = a.id
                WHERE t.user_id = ? 
                AND t.effective_date BETWEEN ? AND ?
                AND " . ($costCenterId == 0 ? "t.cost_center_id IS NULL" : "t.cost_center_id = ?") . "
                AND (c.id = ? OR c.parent_id = ?)
                AND t.deleted_at IS NULL
                AND {$this->excludeTransfers()}
                GROUP BY c.id, c.name, c.icon, c.color, COALESCE(a.currency, 'EUR')
                ORDER BY expense DESC
            ", $costCenterId == 0 
                ? [$this->userId, $startDate, $endDate, $categoryId, $categoryId]
                : [$this->userId, $startDate, $endDate, $costCenterId, $categoryId, $categoryId]
            );
        }
        // Nível 2: Categorias pai de um centro de custo específico
        else if ($costCenterId !== null) {
            $data = DB::select("
                SELECT 
                    COALESCE(c.parent_id, c.id) as category_id,
                    COALESCE(cp.name, c.name) as category_name,
                    COALESCE(cp.icon, c.icon) as category_icon,
                    COALESCE(cp.color, c.color) as category_color,
                    COALESCE(a.currency, 'EUR') as currency,
                    SUM(CASE WHEN t.type = 'credit' THEN t.amount ELSE 0 END) as income,
                    SUM(CASE WHEN t.type = 'debit' THEN ABS(t.amount) ELSE 0 END) as expense
                FROM transactions t
                LEFT JOIN categories c ON t.category_id = c.id
                LEFT JOIN categories cp ON c.parent_id = cp.id
                LEFT JOIN accounts a ON t.account_id = a.id
                WHERE t.user_id = ? 
                AND t.effective_date BETWEEN ? AND ?
                AND " . ($costCenterId == 0 ? "t.cost_center_id IS NULL" : "t.cost_center_id = ?") . "
                AND t.deleted_at IS NULL
                AND {$this->excludeTransfers()}
                GROUP BY COALESCE(c.parent_id, c.id), COALESCE(cp.name, c.name), COALESCE(cp.icon, c.icon), COALESCE(cp.color, c.color), COALESCE(a.currency, 'EUR')
                ORDER BY expense DESC
            ", $costCenterId == 0 
                ? [$this->userId, $startDate, $endDate]
                : [$this->userId, $startDate, $endDate, $costCenterId]
            );
        }
        // Nível 1: Centros de custo
        else {
            $data = DB::select("
                SELECT 
                    COALESCE(cc.id, 0) as cost_center_id,
                    COALESCE(cc.name, 'Sem classificar') as cost_center_name,
                    COALESCE(cc.color, '#6b7280') as color,
                    COALESCE(a.currency, 'EUR') as currency,
                    SUM(CASE WHEN t.type = 'credit' THEN t.amount ELSE 0 END) as income,
                    SUM(CASE WHEN t.type = 'debit' THEN ABS(t.amount) ELSE 0 END) as expense
                FROM transactions t
                LEFT JOIN cost_centers cc ON t.cost_center_id = cc.id
                LEFT JOIN accounts a ON t.account_id = a.id
                WHERE t.user_id = ? 
                AND t.effective_date BETWEEN ? AND ?
                AND t.deleted_at IS NULL
                AND {$this->excludeTransfers()}
                GROUP BY COALESCE(cc.id, 0), COALESCE(cc.name, 'Sem classificar'), COALESCE(cc.color, '#6b7280'), COALESCE(a.currency, 'EUR')
                ORDER BY expense DESC
            ", [$this->userId, $startDate, $endDate]);
        }
        
        // Agrupar y procesar resultados
        $results = [];
        
        // Nível 3: Subcategorias
        if ($costCenterId !== null && $categoryId !== null) {
            foreach ($data as $row) {
                $id = $row->category_id ?? 0;
                if (!isset($results[$id])) {
                    $results[$id] = [
                        'id' => $id == 0 ? null : $id,
                        'name' => $row->category_name ?? 'Sin categoría',
                        'icon' => $row->category_icon ?? 'bi-tag',
                        'color' => $row->category_color ?? '#6b7280',
                        'income_converted' => 0,
                        'expense_converted' => 0,
                    ];
                }
                $results[$id]['income_converted'] += $this->convertToPrimaryCurrency($row->income, $row->currency);
                $results[$id]['expense_converted'] += $this->convertToPrimaryCurrency($row->expense, $row->currency);
            }
        }
        // Nível 2: Categorias
        else if ($costCenterId !== null) {
            foreach ($data as $row) {
                $id = $row->category_id ?? 0;
                if (!isset($results[$id])) {
                    $results[$id] = [
                        'id' => $id == 0 ? null : $id,
                        'name' => $row->category_name ?? 'Sin categoría',
                        'icon' => $row->category_icon ?? 'bi-tag',
                        'color' => $row->category_color ?? '#6b7280',
                        'income_converted' => 0,
                        'expense_converted' => 0,
                    ];
                }
                $results[$id]['income_converted'] += $this->convertToPrimaryCurrency($row->income, $row->currency);
                $results[$id]['expense_converted'] += $this->convertToPrimaryCurrency($row->expense, $row->currency);
            }
        }
        // Nível 1: Centros de custo
        else {
            foreach ($data as $row) {
                $id = $row->cost_center_id;
                if (!isset($results[$id])) {
                    $results[$id] = [
                        'id' => $id == 0 ? null : $id,
                        'name' => $row->cost_center_name,
                        'color' => $row->color,
                        'income_converted' => 0,
                        'expense_converted' => 0,
                    ];
                }
                $results[$id]['income_converted'] += $this->convertToPrimaryCurrency($row->income, $row->currency);
                $results[$id]['expense_converted'] += $this->convertToPrimaryCurrency($row->expense, $row->currency);
            }
        }
        
        $result = array_map(function($item) {
            return [
                'id' => $item['id'],
                'name' => $item['name'],
                'icon' => $item['icon'] ?? null,
                'color' => $item['color'],
                'income' => round($item['income_converted'], 2),
                'expense' => round($item['expense_converted'], 2),
                'balance' => round($item['income_converted'] - $item['expense_converted'], 2),
            ];
        }, $results);
        
        usort($result, fn($a, $b) => $b['expense'] <=> $a['expense']);
        
        return response()->json([
            'data' => array_values($result),
            'currency' => $this->primaryCurrency,
            'total_income' => round(array_sum(array_column($result, 'income')), 2),
            'total_expense' => round(array_sum(array_column($result, 'expense')), 2),
        ]);
    }

    /**
     * Reporte de evolución mensual con multi-divisa
     */
    public function monthlyEvolution(Request $request)
    {
        $this->init();
        $months = $request->get('months', 12);
        $startDate = now()->subMonths($months)->startOfMonth()->format('Y-m-d');
        
        $data = DB::select("
            SELECT 
                DATE_FORMAT(t.effective_date, '%Y-%m') as month,
                COALESCE(a.currency, 'EUR') as currency,
                SUM(CASE WHEN t.type = 'credit' THEN t.amount ELSE 0 END) as income,
                SUM(CASE WHEN t.type = 'debit' THEN ABS(t.amount) ELSE 0 END) as expense
            FROM transactions t
            LEFT JOIN accounts a ON t.account_id = a.id
            WHERE t.user_id = ?
            AND t.effective_date >= ?
            AND t.deleted_at IS NULL
            AND {$this->excludeTransfers()}
            GROUP BY DATE_FORMAT(t.effective_date, '%Y-%m'), COALESCE(a.currency, 'EUR')
            ORDER BY month
        ", [$this->userId, $startDate]);
        
        // Agrupar por mes y convertir
        $byMonth = [];
        foreach ($data as $row) {
            if (!isset($byMonth[$row->month])) {
                $byMonth[$row->month] = [
                    'month' => $row->month,
                    'income_converted' => 0,
                    'expense_converted' => 0,
                    'by_currency' => [],
                ];
            }
            $byMonth[$row->month]['by_currency'][$row->currency] = [
                'income' => round($row->income, 2),
                'expense' => round($row->expense, 2),
            ];
            $byMonth[$row->month]['income_converted'] += $this->convertToPrimaryCurrency($row->income, $row->currency);
            $byMonth[$row->month]['expense_converted'] += $this->convertToPrimaryCurrency($row->expense, $row->currency);
        }
        
        $result = array_map(function($item) {
            $balance = $item['income_converted'] - $item['expense_converted'];
            $savingsRate = $item['income_converted'] > 0 ? round(($balance / $item['income_converted']) * 100, 1) : 0;
            return [
                'month' => $item['month'],
                'month_label' => Carbon::parse($item['month'] . '-01')->isoFormat('MMM YYYY'),
                'income' => round($item['income_converted'], 2),
                'expense' => round($item['expense_converted'], 2),
                'balance' => round($balance, 2),
                'savings_rate' => $savingsRate,
                'by_currency' => $item['by_currency'],
            ];
        }, $byMonth);
        
        $resultArray = array_values($result);
        $avgIncome = count($resultArray) > 0 ? array_sum(array_column($resultArray, 'income')) / count($resultArray) : 0;
        $avgExpense = count($resultArray) > 0 ? array_sum(array_column($resultArray, 'expense')) / count($resultArray) : 0;
        $avgBalance = count($resultArray) > 0 ? array_sum(array_column($resultArray, 'balance')) / count($resultArray) : 0;
        $avgSavings = count($resultArray) > 0 ? array_sum(array_column($resultArray, 'savings_rate')) / count($resultArray) : 0;
        
        return response()->json([
            'data' => $resultArray,
            'currency' => $this->primaryCurrency,
            'averages' => [
                'income' => round($avgIncome, 2),
                'expense' => round($avgExpense, 2),
                'balance' => round($avgBalance, 2),
                'savings_rate' => round($avgSavings, 1),
            ],
        ]);
    }

    /**
     * Reporte de gastos por día de la semana con multi-divisa
     */
    public function byDayOfWeek(Request $request)
    {
        $this->init();
        $months = $request->get('months', 6);
        $startDate = now()->subMonths($months)->format('Y-m-d');
        
        $data = DB::select("
            SELECT 
                DAYOFWEEK(t.effective_date) as day_num,
                COALESCE(a.currency, 'EUR') as currency,
                COUNT(*) as count,
                SUM(ABS(t.amount)) as total
            FROM transactions t
            LEFT JOIN accounts a ON t.account_id = a.id
            WHERE t.user_id = ?
            AND t.type = 'debit'
            AND t.effective_date >= ?
            AND t.deleted_at IS NULL
            AND {$this->excludeTransfers()}
            GROUP BY DAYOFWEEK(t.effective_date), COALESCE(a.currency, 'EUR')
        ", [$this->userId, $startDate]);
        
        // Agrupar por día
        $byDay = [];
        for ($i = 1; $i <= 7; $i++) {
            $byDay[$i] = ['count' => 0, 'total_converted' => 0, 'by_currency' => []];
        }
        
        foreach ($data as $row) {
            $byDay[$row->day_num]['count'] += $row->count;
            $byDay[$row->day_num]['total_converted'] += $this->convertToPrimaryCurrency($row->total, $row->currency);
            if (!isset($byDay[$row->day_num]['by_currency'][$row->currency])) {
                $byDay[$row->day_num]['by_currency'][$row->currency] = 0;
            }
            $byDay[$row->day_num]['by_currency'][$row->currency] += round($row->total, 2);
        }
        
        // Mapeo de días que el frontend traducirá
        $dayKeys = ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday'];
        
        $result = [];
        foreach ($byDay as $dayNum => $info) {
            $result[] = [
                'day_key' => $dayKeys[$dayNum - 1],
                'day_num' => $dayNum,
                'count' => $info['count'],
                'total' => round($info['total_converted'], 2),
                'average' => $info['count'] > 0 ? round($info['total_converted'] / $info['count'], 2) : 0,
                'by_currency' => $info['by_currency'],
            ];
        }
        
        return response()->json([
            'data' => $result,
            'currency' => $this->primaryCurrency,
        ]);
    }

    /**
     * Top gastos con multi-divisa
     */
    public function topExpenses(Request $request)
    {
        $this->init();
        $startDate = $request->get('start_date', now()->startOfMonth()->format('Y-m-d'));
        $endDate = $request->get('end_date', now()->format('Y-m-d'));
        $limit = $request->get('limit', 20);
        
        $data = DB::select("
            SELECT 
                t.id,
                t.description,
                ABS(t.amount) as amount,
                t.effective_date,
                COALESCE(a.currency, 'EUR') as currency,
                c.name as category_name,
                c.icon as category_icon,
                c.color as category_color,
                a.name as account_name,
                cc.name as cost_center_name
            FROM transactions t
            LEFT JOIN accounts a ON t.account_id = a.id
            LEFT JOIN categories c ON t.category_id = c.id
            LEFT JOIN cost_centers cc ON t.cost_center_id = cc.id
            WHERE t.user_id = ?
            AND t.type = 'debit'
            AND t.effective_date BETWEEN ? AND ?
            AND t.deleted_at IS NULL
            AND {$this->excludeTransfers()}
            ORDER BY ABS(t.amount) DESC
            LIMIT ?
        ", [$this->userId, $startDate, $endDate, $limit]);
        
        $totalConverted = 0;
        $result = array_map(function($row) use (&$totalConverted) {
            $converted = $this->convertToPrimaryCurrency($row->amount, $row->currency);
            $totalConverted += $converted;
            return [
                'id' => $row->id,
                'description' => $row->description,
                'amount' => round($row->amount, 2),
                'amount_converted' => round($converted, 2),
                'currency' => $row->currency,
                'date' => $row->effective_date,
                'category' => $row->category_name,
                'category_icon' => $row->category_icon,
                'category_color' => $row->category_color,
                'account' => $row->account_name,
                'cost_center' => $row->cost_center_name,
            ];
        }, $data);
        
        return response()->json([
            'data' => $result,
            'total' => round($totalConverted, 2),
            'currency' => $this->primaryCurrency,
            'period' => ['start' => $startDate, 'end' => $endDate],
        ]);
    }

    /**
     * Comparativa de períodos con multi-divisa
     */
    public function comparePeriods(Request $request)
    {
        $this->init();
        $period1Start = $request->get('period1_start');
        $period1End = $request->get('period1_end');
        $period2Start = $request->get('period2_start');
        $period2End = $request->get('period2_end');
        
        if (!$period1Start) {
            $period1Start = now()->startOfMonth()->format('Y-m-d');
            $period1End = now()->endOfMonth()->format('Y-m-d');
            $period2Start = now()->subMonth()->startOfMonth()->format('Y-m-d');
            $period2End = now()->subMonth()->endOfMonth()->format('Y-m-d');
        }
        
        $getPeriodData = function($start, $end) {
            $data = DB::select("
                SELECT 
                    COALESCE(a.currency, 'EUR') as currency,
                    SUM(CASE WHEN t.type = 'credit' THEN t.amount ELSE 0 END) as income,
                    SUM(CASE WHEN t.type = 'debit' THEN ABS(t.amount) ELSE 0 END) as expense,
                    COUNT(*) as transactions
                FROM transactions t
                LEFT JOIN accounts a ON t.account_id = a.id
                WHERE t.user_id = ?
                AND t.effective_date BETWEEN ? AND ?
                AND t.deleted_at IS NULL
                AND {$this->excludeTransfers()}
                GROUP BY COALESCE(a.currency, 'EUR')
            ", [$this->userId, $start, $end]);
            
            $income = 0;
            $expense = 0;
            $transactions = 0;
            $byCurrency = [];
            
            foreach ($data as $row) {
                $income += $this->convertToPrimaryCurrency($row->income, $row->currency);
                $expense += $this->convertToPrimaryCurrency($row->expense, $row->currency);
                $transactions += $row->transactions;
                $byCurrency[$row->currency] = [
                    'income' => round($row->income, 2),
                    'expense' => round($row->expense, 2),
                ];
            }
            
            return [
                'income' => round($income, 2),
                'expense' => round($expense, 2),
                'balance' => round($income - $expense, 2),
                'transactions' => $transactions,
                'by_currency' => $byCurrency,
            ];
        };
        
        $period1Data = $getPeriodData($period1Start, $period1End);
        $period2Data = $getPeriodData($period2Start, $period2End);
        
        $calcVariation = function($current, $previous) {
            return $previous > 0 ? round((($current - $previous) / $previous) * 100, 1) : 0;
        };
        
        return response()->json([
            'period1' => array_merge([
                'label' => Carbon::parse($period1Start)->isoFormat('MMM YYYY'),
                'start' => $period1Start,
                'end' => $period1End,
            ], $period1Data),
            'period2' => array_merge([
                'label' => Carbon::parse($period2Start)->isoFormat('MMM YYYY'),
                'start' => $period2Start,
                'end' => $period2End,
            ], $period2Data),
            'variation' => [
                'income' => $calcVariation($period1Data['income'], $period2Data['income']),
                'expense' => $calcVariation($period1Data['expense'], $period2Data['expense']),
                'balance' => $calcVariation($period1Data['balance'], $period2Data['balance']),
            ],
            'currency' => $this->primaryCurrency,
        ]);
    }

    /**
     * Reporte de cuentas con multi-divisa
     */
    public function accountsReport(Request $request)
    {
        $this->init();
        
        $accounts = Account::where('user_id', $this->userId)->get();
        
        $result = [];
        $totalBalanceConverted = 0;
        
        foreach ($accounts as $account) {
            // Movimientos del mes (excluyendo transferencias)
            $monthStats = DB::select("
                SELECT 
                    SUM(CASE WHEN type = 'credit' THEN amount ELSE 0 END) as income,
                    SUM(CASE WHEN type = 'debit' THEN ABS(amount) ELSE 0 END) as expense
                FROM transactions
                WHERE account_id = ?
                AND MONTH(effective_date) = ?
                AND YEAR(effective_date) = ?
                AND deleted_at IS NULL
                AND {$this->excludeTransfers()}
            ", [$account->id, now()->month, now()->year]);
            
            $stats = $monthStats[0] ?? (object)['income' => 0, 'expense' => 0];
            
            // Últimas transacciones
            $recent = DB::select("
                SELECT id, description, amount, type, effective_date
                FROM transactions
                WHERE account_id = ?
                AND deleted_at IS NULL
                ORDER BY effective_date DESC
                LIMIT 5
            ", [$account->id]);
            
            $balanceConverted = $this->convertToPrimaryCurrency($account->current_balance, $account->currency);
            if ($account->include_in_total) {
                $totalBalanceConverted += $balanceConverted;
            }
            
            $result[] = [
                'id' => $account->id,
                'name' => $account->name,
                'type' => $account->type,
                'currency' => $account->currency,
                'balance' => round($account->current_balance, 2),
                'balance_converted' => round($balanceConverted, 2),
                'include_in_total' => $account->include_in_total,
                'month_income' => round($stats->income ?? 0, 2),
                'month_expense' => round($stats->expense ?? 0, 2),
                'recent_activity' => array_map(function($t) {
                    return [
                        'id' => $t->id,
                        'description' => $t->description,
                        'amount' => $t->amount,
                        'type' => $t->type,
                        'date' => $t->effective_date,
                    ];
                }, $recent),
            ];
        }
        
        return response()->json([
            'accounts' => $result,
            'currency' => $this->primaryCurrency,
            'summary' => [
                'total_accounts' => count($accounts),
                'total_balance_converted' => round($totalBalanceConverted, 2),
            ],
        ]);
    }

    /**
     * Proyección de gastos con multi-divisa
     */
    public function projection(Request $request)
    {
        $this->init();
        $months = $request->get('months', 3);
        $startDate = now()->subMonths($months)->startOfMonth()->format('Y-m-d');
        $endMonthStart = now()->startOfMonth()->format('Y-m-d');
        $today = now()->format('Y-m-d');
        $endOfMonth = now()->endOfMonth()->format('Y-m-d');
        
        // =========================================================================
        // 1. HISTÓRICO: Média mensal dos últimos N meses
        // =========================================================================
        $historical = DB::select("
            SELECT 
                COALESCE(a.currency, 'EUR') as currency,
                SUM(CASE WHEN t.type = 'credit' THEN t.amount ELSE 0 END) / ? as monthly_income,
                SUM(CASE WHEN t.type = 'debit' THEN ABS(t.amount) ELSE 0 END) / ? as monthly_expense
            FROM transactions t
            LEFT JOIN accounts a ON t.account_id = a.id
            WHERE t.user_id = ?
            AND t.effective_date >= ?
            AND t.effective_date < ?
            AND t.deleted_at IS NULL
            AND {$this->excludeTransfers()}
            GROUP BY COALESCE(a.currency, 'EUR')
        ", [$months, $months, $this->userId, $startDate, $endMonthStart]);
        
        $histIncome = 0;
        $histExpense = 0;
        foreach ($historical as $row) {
            $histIncome += $this->convertToPrimaryCurrency($row->monthly_income, $row->currency);
            $histExpense += $this->convertToPrimaryCurrency($row->monthly_expense, $row->currency);
        }
        
        // =========================================================================
        // 2. MÊS ATUAL: Transações já realizadas (effective_date)
        // =========================================================================
        $current = DB::select("
            SELECT 
                COALESCE(a.currency, 'EUR') as currency,
                SUM(CASE WHEN t.type = 'credit' THEN t.amount ELSE 0 END) as income,
                SUM(CASE WHEN t.type = 'debit' THEN ABS(t.amount) ELSE 0 END) as expense
            FROM transactions t
            LEFT JOIN accounts a ON t.account_id = a.id
            WHERE t.user_id = ?
            AND MONTH(t.effective_date) = ?
            AND YEAR(t.effective_date) = ?
            AND t.deleted_at IS NULL
            AND {$this->excludeTransfers()}
            GROUP BY COALESCE(a.currency, 'EUR')
        ", [$this->userId, now()->month, now()->year]);
        
        $currIncome = 0;
        $currExpense = 0;
        foreach ($current as $row) {
            $currIncome += $this->convertToPrimaryCurrency($row->income, $row->currency);
            $currExpense += $this->convertToPrimaryCurrency($row->expense, $row->currency);
        }
        
        $daysElapsed = max(1, now()->day);
        $daysInMonth = now()->daysInMonth;
        $daysRemaining = $daysInMonth - $daysElapsed;
        
        // =========================================================================
        // 3. RECORRÊNCIAS PENDENTES: Até o fim do mês
        // =========================================================================
        $recurringIncome = 0;
        $recurringExpense = 0;
        
        $recurrences = DB::select("
            SELECT 
                rt.id,
                rt.name,
                rt.planned_amount,
                rt.type,
                rt.frequency,
                rt.day_of_month,
                rt.start_date,
                rt.end_date,
                rt.last_generated_date,
                COALESCE(a.currency, 'EUR') as currency
            FROM recurring_templates rt
            LEFT JOIN accounts a ON rt.account_id = a.id
            WHERE rt.user_id = ?
            AND rt.is_active = 1
            AND rt.deleted_at IS NULL
            AND (rt.end_date IS NULL OR rt.end_date >= ?)
        ", [$this->userId, $today]);
        
        foreach ($recurrences as $rec) {
            // Verificar se ainda vai executar este mês
            $nextDates = $this->getNextRecurrenceDates($rec, $today, $endOfMonth);
            
            foreach ($nextDates as $nextDate) {
                $amount = abs($rec->planned_amount);
                $converted = $this->convertToPrimaryCurrency($amount, $rec->currency);
                
                if ($rec->type === 'credit') {
                    $recurringIncome += $converted;
                } else {
                    $recurringExpense += $converted;
                }
            }
        }
        
        // =========================================================================
        // 4. PASSIVOS PENDENTES: Parcelas até o fim do mês
        // =========================================================================
        $liabilityExpense = 0;
        
        $pendingInstallments = DB::select("
            SELECT 
                li.installment_amount as amount,
                la.currency
            FROM liability_installments li
            JOIN liability_accounts la ON li.liability_account_id = la.id
            WHERE la.user_id = ?
            AND li.status = 'pending'
            AND li.due_date >= ?
            AND li.due_date <= ?
            AND li.deleted_at IS NULL
        ", [$this->userId, $today, $endOfMonth]);
        
        foreach ($pendingInstallments as $row) {
            $liabilityExpense += $this->convertToPrimaryCurrency(abs($row->amount), $row->currency);
        }
        
        // =========================================================================
        // 5. TRANSAÇÕES EM ATRASO (overdue)
        // =========================================================================
        $overdueIncome = 0;
        $overdueExpense = 0;
        
        // Transações pendentes vencidas (status='pending' e planned_date < hoje)
        $overduePendingTransactions = DB::select("
            SELECT 
                t.amount,
                t.type,
                COALESCE(a.currency, 'EUR') as currency
            FROM transactions t
            LEFT JOIN accounts a ON t.account_id = a.id
            WHERE t.user_id = ?
            AND t.status = 'pending'
            AND t.planned_date < ?
            AND t.deleted_at IS NULL
            AND {$this->excludeTransfers()}
        ", [$this->userId, $today]);
        
        foreach ($overduePendingTransactions as $row) {
            $amount = abs($row->amount);
            $converted = $this->convertToPrimaryCurrency($amount, $row->currency);
            
            if ($row->type === 'credit') {
                $overdueIncome += $converted;
            } else {
                $overdueExpense += $converted;
            }
        }
        
        // Parcelas de passivos vencidas
        $overdueInstallments = DB::select("
            SELECT li.installment_amount as amount, la.currency
            FROM liability_installments li
            JOIN liability_accounts la ON li.liability_account_id = la.id
            WHERE la.user_id = ?
            AND li.status != 'paid'
            AND li.due_date < ?
            AND li.deleted_at IS NULL
        ", [$this->userId, $today]);
        
        foreach ($overdueInstallments as $row) {
            $overdueExpense += $this->convertToPrimaryCurrency(abs($row->amount), $row->currency);
        }
        
        // Recorrências que deveriam ter executado mas não executaram
        $overdueRecurrences = $this->getOverdueRecurrences($today);
        foreach ($overdueRecurrences as $rec) {
            $overdueExpense += $this->convertToPrimaryCurrency($rec['amount'], $rec['currency']);
        }
        
        // Instâncias de recorrências pendentes vencidas
        $overdueRecurringInstances = DB::select("
            SELECT 
                ri.planned_amount as amount,
                rt.type,
                COALESCE(a.currency, 'EUR') as currency
            FROM recurring_instances ri
            JOIN recurring_templates rt ON ri.recurring_template_id = rt.id
            LEFT JOIN accounts a ON rt.account_id = a.id
            WHERE ri.user_id = ?
            AND ri.status = 'pending'
            AND ri.due_date < ?
            AND ri.deleted_at IS NULL
        ", [$this->userId, $today]);
        
        foreach ($overdueRecurringInstances as $row) {
            $amount = abs($row->amount);
            $converted = $this->convertToPrimaryCurrency($amount, $row->currency);
            
            if ($row->type === 'credit') {
                $overdueIncome += $converted;
            } else {
                $overdueExpense += $converted;
            }
        }
        
        // =========================================================================
        // 6. TRANSAÇÕES PENDENTES ATÉ O FIM DO MÊS (planned_date entre hoje e fim do mês)
        // =========================================================================
        $pendingIncome = 0;
        $pendingExpense = 0;
        
        $pendingTransactions = DB::select("
            SELECT 
                t.amount,
                t.type,
                COALESCE(a.currency, 'EUR') as currency
            FROM transactions t
            LEFT JOIN accounts a ON t.account_id = a.id
            WHERE t.user_id = ?
            AND t.status = 'pending'
            AND t.planned_date >= ?
            AND t.planned_date <= ?
            AND t.deleted_at IS NULL
            AND {$this->excludeTransfers()}
        ", [$this->userId, $today, $endOfMonth]);
        
        foreach ($pendingTransactions as $row) {
            $amount = abs($row->amount);
            $converted = $this->convertToPrimaryCurrency($amount, $row->currency);
            
            if ($row->type === 'credit') {
                $pendingIncome += $converted;
            } else {
                $pendingExpense += $converted;
            }
        }
        
        // =========================================================================
        // 7. CÁLCULOS FINAIS DA PROJEÇÃO
        // =========================================================================
        
        // Projeção simples (extrapolação linear)
        $simpleProjectedExpense = ($currExpense / $daysElapsed) * $daysInMonth;
        $simpleProjectedIncome = ($currIncome / $daysElapsed) * $daysInMonth;
        
        // Projeção inteligente: realizado + pendente (todos os tipos)
        $smartProjectedIncome = $currIncome + $recurringIncome + $overdueIncome + $pendingIncome;
        $smartProjectedExpense = $currExpense + $recurringExpense + $liabilityExpense + $overdueExpense + $pendingExpense;
        
        return response()->json([
            'historical_average' => [
                'income' => round($histIncome, 2),
                'expense' => round($histExpense, 2),
                'balance' => round($histIncome - $histExpense, 2),
            ],
            'current_month' => [
                'income' => round($currIncome, 2),
                'expense' => round($currExpense, 2),
                'balance' => round($currIncome - $currExpense, 2),
                'days_elapsed' => $daysElapsed,
                'days_remaining' => $daysRemaining,
            ],
            'pending_this_month' => [
                'recurring_income' => round($recurringIncome, 2),
                'recurring_expense' => round($recurringExpense, 2),
                'liability_installments' => round($liabilityExpense, 2),
                'pending_income' => round($pendingIncome, 2),
                'pending_expense' => round($pendingExpense, 2),
                'total_pending_income' => round($recurringIncome + $pendingIncome, 2),
                'total_pending_expense' => round($recurringExpense + $liabilityExpense + $pendingExpense, 2),
            ],
            'overdue' => [
                'income' => round($overdueIncome, 2),
                'expense' => round($overdueExpense, 2),
                'total' => round($overdueExpense - $overdueIncome, 2),
            ],
            'projection' => [
                // Valores principais (usa projeção inteligente)
                'income' => round($smartProjectedIncome, 2),
                'expense' => round($smartProjectedExpense, 2),
                'balance' => round($smartProjectedIncome - $smartProjectedExpense, 2),
                // Projeção simples (extrapolação linear)
                'simple' => [
                    'income' => round($simpleProjectedIncome, 2),
                    'expense' => round($simpleProjectedExpense, 2),
                    'balance' => round($simpleProjectedIncome - $simpleProjectedExpense, 2),
                ],
                // Projeção inteligente (realizado + recorrências + passivos)
                'smart' => [
                    'income' => round($smartProjectedIncome, 2),
                    'expense' => round($smartProjectedExpense, 2),
                    'balance' => round($smartProjectedIncome - $smartProjectedExpense, 2),
                ],
            ],
            'vs_average' => [
                'income' => $histIncome > 0 ? round((($smartProjectedIncome - $histIncome) / $histIncome) * 100, 1) : 0,
                'expense' => $histExpense > 0 ? round((($smartProjectedExpense - $histExpense) / $histExpense) * 100, 1) : 0,
            ],
            'currency' => $this->primaryCurrency,
        ]);
    }

    /**
     * Projeção de saldo para gráfico com dados diários/semanais
     */
    public function projectionChart(Request $request)
    {
        $this->init();
        
        $months = (int) min(max($request->input('months', 3), 1), 12);
        $today = Carbon::today();
        $endDate = $today->copy()->addMonths($months);
        
        // Obter saldo atual total das contas (simplificado - assumindo mesma moeda)
        $currentBalance = DB::selectOne("
            SELECT COALESCE(SUM(current_balance), 0) as total
            FROM accounts 
            WHERE user_id = ? 
            AND include_in_total = 1
            AND deleted_at IS NULL
        ", [$this->userId])->total ?? 0;
        
        // Gerar pontos de dados (diário para até 3 meses, semanal para mais)
        $dataPoints = [];
        $runningBalance = (float) $currentBalance;
        $interval = $months <= 3 ? 'day' : 'week';
        $current = $today->copy();
        
        // Buscar recorrências ativas
        $recurrences = DB::select("
            SELECT 
                rt.id,
                rt.name,
                rt.planned_amount,
                rt.type,
                rt.frequency,
                rt.day_of_month,
                rt.start_date,
                rt.end_date,
                COALESCE(a.currency, 'EUR') as currency
            FROM recurring_templates rt
            LEFT JOIN accounts a ON rt.account_id = a.id
            WHERE rt.user_id = ?
            AND rt.is_active = 1
            AND rt.deleted_at IS NULL
        ", [$this->userId]);
        
        // Buscar parcelas de passivos pendentes
        $liabilityInstallments = DB::select("
            SELECT 
                li.due_date,
                li.installment_amount as amount,
                la.currency
            FROM liability_installments li
            JOIN liability_accounts la ON li.liability_account_id = la.id
            WHERE la.user_id = ?
            AND li.status = 'pending'
            AND li.due_date >= ?
            AND li.due_date <= ?
            AND li.deleted_at IS NULL
            ORDER BY li.due_date
        ", [$this->userId, $today->toDateString(), $endDate->toDateString()]);
        
        // Buscar transações agendadas/pendentes (incluindo atrasadas)
        $scheduledTransactions = DB::select("
            SELECT 
                t.effective_date as date,
                t.amount,
                t.type,
                COALESCE(a.currency, 'EUR') as currency,
                CASE WHEN t.effective_date < ? THEN 1 ELSE 0 END as is_overdue
            FROM transactions t
            LEFT JOIN accounts a ON t.account_id = a.id
            WHERE t.user_id = ?
            AND t.status IN ('pending', 'scheduled')
            AND t.effective_date <= ?
            AND t.deleted_at IS NULL
            ORDER BY t.effective_date
        ", [$today->toDateString(), $this->userId, $endDate->toDateString()]);
        
        // Separar transações atrasadas para processar primeiro
        $overdueTransactions = array_filter($scheduledTransactions, fn($tx) => $tx->is_overdue);
        $futureTransactions = array_filter($scheduledTransactions, fn($tx) => !$tx->is_overdue);
        
        // Processar transações atrasadas ANTES do ponto inicial
        foreach ($overdueTransactions as $tx) {
            $amount = $this->convertToPrimaryCurrency(abs($tx->amount), $tx->currency);
            if ($tx->type === 'credit') {
                $currentBalance += $amount;
            } else {
                $currentBalance -= $amount;
            }
            $runningBalance = $currentBalance;
        }
        
        // Ponto inicial (já inclui o impacto das transações atrasadas)
        $dataPoints[] = [
            'date' => $today->toDateString(),
            'balance' => round($runningBalance, 2),
            'label' => $today->format('d/m'),
            'isToday' => true,
            'has_overdue' => count($overdueTransactions) > 0,
        ];
        
        // Gerar pontos até a data final
        while ($current->lt($endDate)) {
            if ($interval === 'day') {
                $current->addDay();
            } else {
                $current->addWeek();
            }
            
            if ($current->gt($endDate)) break;
            
            $periodStart = $dataPoints[count($dataPoints) - 1]['date'];
            $periodEnd = $current->toDateString();
            
            // Somar recorrências neste período
            foreach ($recurrences as $rec) {
                $dates = $this->getNextRecurrenceDates($rec, $periodStart, $periodEnd);
                foreach ($dates as $date) {
                    $amount = $this->convertToPrimaryCurrency(abs($rec->planned_amount), $rec->currency);
                    if ($rec->type === 'credit') {
                        $runningBalance += $amount;
                    } else {
                        $runningBalance -= $amount;
                    }
                }
            }
            
            // Somar parcelas de passivos neste período
            foreach ($liabilityInstallments as $inst) {
                if ($inst->due_date > $periodStart && $inst->due_date <= $periodEnd) {
                    $amount = $this->convertToPrimaryCurrency(abs($inst->amount), $inst->currency);
                    $runningBalance -= $amount;
                }
            }
            
            // Somar transações agendadas neste período (apenas futuras)
            foreach ($futureTransactions as $tx) {
                if ($tx->date > $periodStart && $tx->date <= $periodEnd) {
                    $amount = $this->convertToPrimaryCurrency(abs($tx->amount), $tx->currency);
                    if ($tx->type === 'credit') {
                        $runningBalance += $amount;
                    } else {
                        $runningBalance -= $amount;
                    }
                }
            }
            
            $dataPoints[] = [
                'date' => $current->toDateString(),
                'balance' => round($runningBalance, 2),
                'label' => $current->format('d/m'),
                'isToday' => false,
            ];
        }
        
        // Calcular estatísticas
        $balances = array_column($dataPoints, 'balance');
        $minBalance = min($balances);
        $maxBalance = max($balances);
        $avgBalance = array_sum($balances) / count($balances);
        $finalBalance = end($balances);
        
        // Detectar mês de saldo negativo (se houver)
        $negativeMonth = null;
        foreach ($dataPoints as $point) {
            if ($point['balance'] < 0) {
                $negativeMonth = Carbon::parse($point['date'])->format('M Y');
                break;
            }
        }
        
        return response()->json([
            'data' => $dataPoints,
            'summary' => [
                'current_balance' => round($currentBalance, 2),
                'final_balance' => round($finalBalance, 2),
                'min_balance' => round($minBalance, 2),
                'max_balance' => round($maxBalance, 2),
                'avg_balance' => round($avgBalance, 2),
                'change' => round($finalBalance - $currentBalance, 2),
                'change_percent' => $currentBalance != 0 ? round((($finalBalance - $currentBalance) / abs($currentBalance)) * 100, 1) : 0,
                'negative_month' => $negativeMonth,
                'overdue_count' => count($overdueTransactions),
                'overdue_impact' => round(array_reduce($overdueTransactions, function($carry, $tx) {
                    $amount = $this->convertToPrimaryCurrency(abs($tx->amount), $tx->currency);
                    return $carry + ($tx->type === 'credit' ? $amount : -$amount);
                }, 0), 2),
            ],
            'period' => [
                'start' => $today->toDateString(),
                'end' => $endDate->toDateString(),
                'months' => $months,
                'interval' => $interval,
                'total_points' => count($dataPoints),
            ],
            'currency' => $this->primaryCurrency,
        ]);
    }

    /**
     * Reporte de recurrencias con multi-divisa
     */
    public function recurringReport(Request $request)
    {
        $this->init();
        
        $templates = RecurringTemplate::where('user_id', $this->userId)
            ->where('is_active', true)
            ->with(['category', 'account'])
            ->get();
        
        $monthlyIncomeConverted = 0;
        $monthlyExpenseConverted = 0;
        $byCurrency = [];
        
        $result = $templates->map(function($t) use (&$monthlyIncomeConverted, &$monthlyExpenseConverted, &$byCurrency) {
            $currency = $t->account ? $t->account->currency : 'EUR';
            // CORRIGIDO: usar planned_amount em vez de amount
            $amount = abs($t->planned_amount ?? 0);
            
            // Converter para valor mensal baseado na frequência
            $monthlyAmount = $this->convertToMonthlyAmount($amount, $t->frequency);
            $converted = $this->convertToPrimaryCurrency($monthlyAmount, $currency);
            
            if (!isset($byCurrency[$currency])) {
                $byCurrency[$currency] = ['income' => 0, 'expense' => 0];
            }
            
            if ($t->type === 'credit') {
                $monthlyIncomeConverted += $converted;
                $byCurrency[$currency]['income'] += $monthlyAmount;
            } else {
                $monthlyExpenseConverted += $converted;
                $byCurrency[$currency]['expense'] += $monthlyAmount;
            }
            
            return [
                'id' => $t->id,
                // CORRIGIDO: usar name em vez de description
                'description' => $t->name ?? $t->transaction_description,
                'amount' => round($amount, 2),
                'monthly_amount' => round($monthlyAmount, 2),
                'amount_converted' => round($converted, 2),
                'currency' => $currency,
                'type' => $t->type,
                'frequency' => $t->frequency,
                'category' => $t->category ? $t->category->name : null,
                'category_icon' => $t->category ? $t->category->icon : null,
                'category_color' => $t->category ? $t->category->color : null,
                'next_date' => $t->next_occurrence_date,
                'account' => $t->account ? $t->account->name : null,
            ];
        });
        
        return response()->json([
            'templates' => $result,
            'currency' => $this->primaryCurrency,
            'summary' => [
                'total_recurring' => $templates->count(),
                'monthly_income' => round($monthlyIncomeConverted, 2),
                'monthly_expense' => round($monthlyExpenseConverted, 2),
                'net_recurring' => round($monthlyIncomeConverted - $monthlyExpenseConverted, 2),
                'by_currency' => $byCurrency,
            ],
        ]);
    }
    
    /**
     * Converte um valor para equivalente mensal baseado na frequência
     */
    private function convertToMonthlyAmount(float $amount, string $frequency): float
    {
        return match($frequency) {
            'daily' => $amount * 30,
            'weekly' => $amount * 4.33,
            'biweekly' => $amount * 2.17,
            'monthly' => $amount,
            'bimonthly' => $amount / 2,
            'quarterly' => $amount / 3,
            'semiannual' => $amount / 6,
            'annual' => $amount / 12,
            default => $amount,
        };
    }

    /**
     * Reporte de pasivos/deudas
     */
    public function liabilities(Request $request)
    {
        $this->init();
        
        $liabilities = LiabilityAccount::where('user_id', $this->userId)
            ->with(['installments' => function($q) {
                $q->orderBy('due_date');
            }])
            ->get();
        
        $totalDebtConverted = 0;
        $totalPaidConverted = 0;
        $totalPendingConverted = 0;
        
        $result = $liabilities->map(function($l) use (&$totalDebtConverted, &$totalPaidConverted, &$totalPendingConverted) {
            $currency = $l->currency ?? 'EUR';
            $totalAmount = $l->total_contract_value ?? 0;
            // Usar installment_amount en lugar de amount
            $paidAmount = $l->installments->where('status', 'paid')->sum('installment_amount');
            $pendingAmount = $l->installments->where('status', '!=', 'paid')->sum('installment_amount');
            
            $totalDebtConverted += $this->convertToPrimaryCurrency($totalAmount, $currency);
            $totalPaidConverted += $this->convertToPrimaryCurrency($paidAmount, $currency);
            $totalPendingConverted += $this->convertToPrimaryCurrency($pendingAmount, $currency);
            
            // Próxima cuota pendiente
            $nextInstallment = $l->installments->where('status', '!=', 'paid')->sortBy('due_date')->first();
            
            // Cuotas vencidas
            $overdueInstallments = $l->installments
                ->where('status', '!=', 'paid')
                ->where('due_date', '<', now()->format('Y-m-d'))
                ->count();
            
            return [
                'id' => $l->id,
                'name' => $l->name,
                'type' => $l->type,
                'currency' => $currency,
                'total_amount' => round($totalAmount, 2),
                'paid_amount' => round($paidAmount, 2),
                'pending_amount' => round($pendingAmount, 2),
                'progress' => $totalAmount > 0 ? round(($paidAmount / $totalAmount) * 100, 1) : 0,
                'total_installments' => $l->installments->count(),
                'paid_installments' => $l->installments->where('status', 'paid')->count(),
                'overdue_installments' => $overdueInstallments,
                'next_installment' => $nextInstallment ? [
                    'amount' => round($nextInstallment->installment_amount, 2),
                    'due_date' => $nextInstallment->due_date,
                    'is_overdue' => $nextInstallment->due_date < now()->format('Y-m-d'),
                ] : null,
                'start_date' => $l->start_date,
                'end_date' => $l->end_date,
            ];
        });
        
        return response()->json([
            'data' => $result,
            'currency' => $this->primaryCurrency,
            'summary' => [
                'total_liabilities' => $liabilities->count(),
                'total_debt' => round($totalDebtConverted, 2),
                'total_paid' => round($totalPaidConverted, 2),
                'total_pending' => round($totalPendingConverted, 2),
                'progress' => $totalDebtConverted > 0 ? round(($totalPaidConverted / $totalDebtConverted) * 100, 1) : 0,
            ],
        ]);
    }

    /**
     * Transacciones futuras programadas
     * Incluye: transacciones pending/scheduled, cuotas de pasivos, y proyecciones de recurrencias
     */
    public function futureTransactions(Request $request)
    {
        $this->init();
        $days = (int) $request->get('days', 30);
        
        $endDate = now()->addDays($days)->format('Y-m-d');
        $today = now()->format('Y-m-d');
        
        $result = [];
        $totalIncomeConverted = 0;
        $totalExpenseConverted = 0;
        
        // 1. Transacciones pendientes/scheduled (usando planned_date)
        $pendingTransactions = DB::select("
            SELECT 
                t.id,
                t.description,
                COALESCE(t.planned_amount, t.amount) as amount,
                t.type,
                t.planned_date as date,
                t.status,
                COALESCE(a.currency, 'EUR') as currency,
                a.name as account_name,
                c.name as category_name,
                c.icon as category_icon,
                cc.name as cost_center_name
            FROM transactions t
            LEFT JOIN accounts a ON t.account_id = a.id
            LEFT JOIN categories c ON t.category_id = c.id
            LEFT JOIN cost_centers cc ON t.cost_center_id = cc.id
            WHERE t.user_id = ?
            AND t.status IN ('pending', 'scheduled')
            AND t.planned_date >= ?
            AND t.planned_date <= ?
            AND t.deleted_at IS NULL
            ORDER BY t.planned_date ASC
        ", [$this->userId, $today, $endDate]);
        
        foreach ($pendingTransactions as $row) {
            $amount = abs($row->amount);
            $converted = $this->convertToPrimaryCurrency($amount, $row->currency);
            
            if ($row->type === 'credit') {
                $totalIncomeConverted += $converted;
            } else {
                $totalExpenseConverted += $converted;
            }
            
            $result[] = [
                'id' => $row->id,
                'description' => $row->description,
                'amount' => round($amount, 2),
                'amount_converted' => round($converted, 2),
                'currency' => $row->currency,
                'type' => $row->type,
                'source_type' => 'transaction',
                'status' => $row->status,
                'date' => $row->date,
                'days_until' => (int) max(0, Carbon::parse($row->date)->diffInDays(now(), false) * -1),
                'account' => $row->account_name,
                'category' => $row->category_name,
                'category_icon' => $row->category_icon,
                'cost_center' => $row->cost_center_name,
            ];
        }
        
        // 2. Cuotas de pasivos pendientes
        $pendingInstallments = DB::select("
            SELECT 
                li.id,
                la.name as description,
                li.installment_amount as amount,
                'debit' as type,
                li.due_date as date,
                li.status,
                la.currency,
                a.name as account_name
            FROM liability_installments li
            JOIN liability_accounts la ON li.liability_account_id = la.id
            LEFT JOIN accounts a ON la.account_id = a.id
            WHERE la.user_id = ?
            AND li.status = 'pending'
            AND li.due_date >= ?
            AND li.due_date <= ?
            AND li.deleted_at IS NULL
            ORDER BY li.due_date ASC
        ", [$this->userId, $today, $endDate]);
        
        foreach ($pendingInstallments as $row) {
            $amount = abs($row->amount);
            $converted = $this->convertToPrimaryCurrency($amount, $row->currency);
            $totalExpenseConverted += $converted;
            
            $result[] = [
                'id' => $row->id,
                'description' => $row->description . ' (Cuota)',
                'amount' => round($amount, 2),
                'amount_converted' => round($converted, 2),
                'currency' => $row->currency,
                'type' => 'debit',
                'source_type' => 'liability_installment',
                'status' => $row->status,
                'date' => $row->date,
                'days_until' => (int) max(0, Carbon::parse($row->date)->diffInDays(now(), false) * -1),
                'account' => $row->account_name,
                'category' => null,
                'category_icon' => null,
                'cost_center' => null,
            ];
        }
        
        // 3. Proyecciones de recurrencias activas
        $recurrences = DB::select("
            SELECT 
                rt.id,
                rt.name,
                rt.transaction_description as description,
                rt.planned_amount as amount,
                rt.type,
                rt.frequency,
                rt.day_of_month,
                rt.start_date,
                rt.end_date,
                COALESCE(a.currency, 'EUR') as currency,
                a.name as account_name,
                c.name as category_name,
                c.icon as category_icon,
                cc.name as cost_center_name
            FROM recurring_templates rt
            LEFT JOIN accounts a ON rt.account_id = a.id
            LEFT JOIN categories c ON rt.category_id = c.id
            LEFT JOIN cost_centers cc ON rt.cost_center_id = cc.id
            WHERE rt.user_id = ?
            AND rt.is_active = 1
            AND rt.deleted_at IS NULL
            AND (rt.end_date IS NULL OR rt.end_date >= ?)
        ", [$this->userId, $today]);
        
        foreach ($recurrences as $rec) {
            // Calcular próximas ejecuciones dentro del período
            $nextDates = $this->getNextRecurrenceDates($rec, $today, $endDate);
            
            foreach ($nextDates as $nextDate) {
                $amount = abs($rec->amount);
                $converted = $this->convertToPrimaryCurrency($amount, $rec->currency);
                
                if ($rec->type === 'credit') {
                    $totalIncomeConverted += $converted;
                } else {
                    $totalExpenseConverted += $converted;
                }
                
                $result[] = [
                    'id' => $rec->id,
                    'description' => $rec->name . ' (Recurrente)',
                    'amount' => round($amount, 2),
                    'amount_converted' => round($converted, 2),
                    'currency' => $rec->currency,
                    'type' => $rec->type,
                    'source_type' => 'recurring',
                    'status' => 'projected',
                    'date' => $nextDate,
                    'days_until' => (int) max(0, Carbon::parse($nextDate)->diffInDays(now(), false) * -1),
                    'account' => $rec->account_name,
                    'category' => $rec->category_name,
                    'category_icon' => $rec->category_icon,
                    'cost_center' => $rec->cost_center_name,
                ];
            }
        }
        
        // Ordenar por fecha
        usort($result, fn($a, $b) => strcmp($a['date'], $b['date']));
        
        return response()->json([
            'data' => $result,
            'currency' => $this->primaryCurrency,
            'summary' => [
                'total_transactions' => count($result),
                'total_income' => round($totalIncomeConverted, 2),
                'total_expense' => round($totalExpenseConverted, 2),
                'net_impact' => round($totalIncomeConverted - $totalExpenseConverted, 2),
            ],
        ]);
    }

    /**
     * Transacciones vencidas (pendientes de pago)
     * Incluye: cuotas de pasivos vencidas, transacciones pendientes/scheduled pasadas,
     * y recurrencias que deberían haber ejecutado pero no lo hicieron
     */
    public function overdueTransactions(Request $request)
    {
        \Log::info('overdueTransactions called');
        try {
            $this->init();
            
            $today = now()->format('Y-m-d');
            $result = [];
            $totalOverdueConverted = 0;
            
            // 1. Cuotas de pasivos vencidas
            $overdueInstallments = DB::select("
                SELECT 
                    li.id,
                    la.name as description,
                li.installment_amount as amount,
                li.due_date,
                la.currency,
                DATEDIFF(?, li.due_date) as days_overdue,
                a.name as account_name
            FROM liability_installments li
            JOIN liability_accounts la ON li.liability_account_id = la.id
            LEFT JOIN accounts a ON la.account_id = a.id
            WHERE la.user_id = ?
            AND li.status != 'paid'
            AND li.due_date < ?
            AND li.deleted_at IS NULL
            ORDER BY li.due_date ASC
        ", [$today, $this->userId, $today]);
        
        foreach ($overdueInstallments as $row) {
            $converted = $this->convertToPrimaryCurrency($row->amount, $row->currency);
            $totalOverdueConverted += $converted;
            
            $result[] = [
                'id' => $row->id,
                'description' => $row->description . ' (Cuota)',
                'amount' => round($row->amount, 2),
                'amount_converted' => round($converted, 2),
                'currency' => $row->currency,
                'due_date' => $row->due_date,
                'days_overdue' => (int) $row->days_overdue,
                'source_type' => 'liability_installment',
                'account' => $row->account_name,
                'category' => null,
            ];
        }
        
        // 2. Transacciones pendientes/scheduled con fecha pasada
        $overdueTransactions = DB::select("
            SELECT 
                t.id,
                t.description,
                COALESCE(t.planned_amount, t.amount) as amount,
                t.planned_date as due_date,
                t.type,
                t.status,
                COALESCE(a.currency, 'EUR') as currency,
                DATEDIFF(?, t.planned_date) as days_overdue,
                a.name as account_name,
                c.name as category_name
            FROM transactions t
            LEFT JOIN accounts a ON t.account_id = a.id
            LEFT JOIN categories c ON t.category_id = c.id
            WHERE t.user_id = ?
            AND t.status IN ('pending', 'scheduled')
            AND t.planned_date < ?
            AND t.deleted_at IS NULL
            ORDER BY t.planned_date ASC
        ", [$today, $this->userId, $today]);
        
        foreach ($overdueTransactions as $row) {
            $amount = abs($row->amount);
            $converted = $this->convertToPrimaryCurrency($amount, $row->currency);
            $totalOverdueConverted += $converted;
            
            $result[] = [
                'id' => $row->id,
                'description' => $row->description,
                'amount' => round($amount, 2),
                'amount_converted' => round($converted, 2),
                'currency' => $row->currency,
                'due_date' => $row->due_date,
                'days_overdue' => (int) $row->days_overdue,
                'source_type' => 'transaction',
                'type' => $row->type,
                'status' => $row->status,
                'account' => $row->account_name,
                'category' => $row->category_name,
            ];
        }
        
        // 3. Recurrencias activas que deberían haber ejecutado pero no lo hicieron
        $overdueRecurrences = $this->getOverdueRecurrences($today);
        foreach ($overdueRecurrences as $rec) {
            $converted = $this->convertToPrimaryCurrency($rec['amount'], $rec['currency']);
            $totalOverdueConverted += $converted;
            $rec['amount_converted'] = round($converted, 2);
            $result[] = $rec;
        }
        
        // Ordenar por días de atraso (más atrasado primero)
        usort($result, fn($a, $b) => $b['days_overdue'] <=> $a['days_overdue']);
        
        return response()->json([
            'data' => $result,
            'currency' => $this->primaryCurrency,
            'summary' => [
                'total_overdue' => count($result),
                'total_amount' => round($totalOverdueConverted, 2),
            ],
        ]);
        } catch (\Throwable $e) {
            \Log::error('overdueTransactions error: ' . $e->getMessage() . ' at line ' . $e->getLine());
            return response()->json([
                'error' => $e->getMessage(),
                'line' => $e->getLine(),
                'file' => $e->getFile()
            ], 500);
        }
    }

    /**
     * Resumen ejecutivo completo
     */
    public function executiveSummary(Request $request)
    {
        $this->init();
        $startDate = $request->get('start_date', now()->startOfYear()->format('Y-m-d'));
        $endDate = $request->get('end_date', now()->format('Y-m-d'));
        
        // Totales del período
        $totals = DB::select("
            SELECT 
                COALESCE(a.currency, 'EUR') as currency,
                SUM(CASE WHEN t.type = 'credit' THEN t.amount ELSE 0 END) as income,
                SUM(CASE WHEN t.type = 'debit' THEN ABS(t.amount) ELSE 0 END) as expense,
                COUNT(*) as transactions
            FROM transactions t
            LEFT JOIN accounts a ON t.account_id = a.id
            WHERE t.user_id = ?
            AND t.effective_date BETWEEN ? AND ?
            AND t.deleted_at IS NULL
            AND {$this->excludeTransfers()}
            GROUP BY COALESCE(a.currency, 'EUR')
        ", [$this->userId, $startDate, $endDate]);
        
        $totalIncome = 0;
        $totalExpense = 0;
        $totalTransactions = 0;
        $byCurrency = [];
        
        foreach ($totals as $row) {
            $totalIncome += $this->convertToPrimaryCurrency($row->income, $row->currency);
            $totalExpense += $this->convertToPrimaryCurrency($row->expense, $row->currency);
            $totalTransactions += $row->transactions;
            $byCurrency[$row->currency] = [
                'income' => round($row->income, 2),
                'expense' => round($row->expense, 2),
            ];
        }
        
        // Top 5 categorías de gasto
        $topCategories = DB::select("
            SELECT 
                c.name,
                c.icon,
                c.color,
                COALESCE(a.currency, 'EUR') as currency,
                SUM(ABS(t.amount)) as total
            FROM transactions t
            LEFT JOIN categories c ON t.category_id = c.id
            LEFT JOIN accounts a ON t.account_id = a.id
            WHERE t.user_id = ?
            AND t.type = 'debit'
            AND t.effective_date BETWEEN ? AND ?
            AND t.deleted_at IS NULL
            AND {$this->excludeTransfers()}
            GROUP BY c.id, c.name, c.icon, c.color, COALESCE(a.currency, 'EUR')
        ", [$this->userId, $startDate, $endDate]);
        
        $categoryTotals = [];
        foreach ($topCategories as $row) {
            $name = $row->name ?? 'Sin categoría';
            if (!isset($categoryTotals[$name])) {
                $categoryTotals[$name] = [
                    'name' => $name,
                    'icon' => $row->icon,
                    'color' => $row->color,
                    'total' => 0,
                ];
            }
            $categoryTotals[$name]['total'] += $this->convertToPrimaryCurrency($row->total, $row->currency);
        }
        usort($categoryTotals, fn($a, $b) => $b['total'] <=> $a['total']);
        $top5Categories = array_slice($categoryTotals, 0, 5);
        
        // Cuentas
        $accounts = Account::where('user_id', $this->userId)
            ->where('include_in_total', true)
            ->get();
        
        $totalBalance = 0;
        foreach ($accounts as $acc) {
            $totalBalance += $this->convertToPrimaryCurrency($acc->current_balance, $acc->currency);
        }
        
        return response()->json([
            'period' => ['start' => $startDate, 'end' => $endDate],
            'currency' => $this->primaryCurrency,
            'totals' => [
                'income' => round($totalIncome, 2),
                'expense' => round($totalExpense, 2),
                'balance' => round($totalIncome - $totalExpense, 2),
                'savings_rate' => $totalIncome > 0 ? round((($totalIncome - $totalExpense) / $totalIncome) * 100, 1) : 0,
                'transactions' => $totalTransactions,
                'by_currency' => $byCurrency,
            ],
            'accounts' => [
                'count' => $accounts->count(),
                'total_balance' => round($totalBalance, 2),
            ],
            'top_expense_categories' => array_map(function($c) {
                return [
                    'name' => $c['name'],
                    'icon' => $c['icon'],
                    'color' => $c['color'],
                    'total' => round($c['total'], 2),
                ];
            }, $top5Categories),
        ]);
    }

    /**
     * Calcula las próximas fechas de ejecución de una recurrencia dentro de un período
     */
    private function getNextRecurrenceDates($recurrence, $startDate, $endDate)
    {
        $dates = [];
        $start = Carbon::parse($startDate);
        $end = Carbon::parse($endDate);
        $recStart = Carbon::parse($recurrence->start_date);
        $recEnd = $recurrence->end_date ? Carbon::parse($recurrence->end_date) : null;
        
        // Si la recurrencia termina antes del período, no hay fechas
        if ($recEnd && $recEnd->lt($start)) {
            return $dates;
        }
        
        // Calcular la primera fecha dentro del período
        $current = $recStart->copy();
        
        // Avanzar hasta estar dentro del período (EXCLUYE startDate para evitar duplicados)
        while ($current->lte($start)) {
            $current = $this->advanceToNextOccurrence($current, $recurrence);
            if ($current->gt($end)) {
                return $dates;
            }
        }
        
        // Generar fechas hasta el fin del período
        $maxIterations = 100; // Prevenir bucles infinitos
        $iterations = 0;
        
        while ($current->lte($end) && $iterations < $maxIterations) {
            // Verificar que no pase de la fecha de fin de la recurrencia
            if ($recEnd && $current->gt($recEnd)) {
                break;
            }
            
            $dates[] = $current->format('Y-m-d');
            $current = $this->advanceToNextOccurrence($current, $recurrence);
            $iterations++;
        }
        
        return $dates;
    }

    /**
     * Avanza a la próxima ocurrencia según la frecuencia
     */
    private function advanceToNextOccurrence($date, $recurrence)
    {
        $next = $date->copy();
        
        switch ($recurrence->frequency) {
            case 'daily':
                $next->addDays($recurrence->frequency_interval ?? 1);
                break;
            case 'weekly':
                $next->addWeeks($recurrence->frequency_interval ?? 1);
                break;
            case 'biweekly':
                $next->addWeeks(2);
                break;
            case 'monthly':
                $next->addMonths($recurrence->frequency_interval ?? 1);
                if ($recurrence->day_of_month) {
                    $next->day = min($recurrence->day_of_month, $next->daysInMonth);
                }
                break;
            case 'bimonthly':
                $next->addMonths(2);
                if ($recurrence->day_of_month) {
                    $next->day = min($recurrence->day_of_month, $next->daysInMonth);
                }
                break;
            case 'quarterly':
                $next->addMonths(3);
                if ($recurrence->day_of_month) {
                    $next->day = min($recurrence->day_of_month, $next->daysInMonth);
                }
                break;
            case 'semiannual':
                $next->addMonths(6);
                if ($recurrence->day_of_month) {
                    $next->day = min($recurrence->day_of_month, $next->daysInMonth);
                }
                break;
            case 'annual':
                $next->addYears($recurrence->frequency_interval ?? 1);
                break;
            default:
                $next->addMonths(1);
        }
        
        return $next;
    }

    /**
     * Obtiene las recurrencias que deberían haber ejecutado pero no lo hicieron
     * Busca la última ejecución esperada y verifica si existe una transacción para esa fecha
     */
    private function getOverdueRecurrences($today)
    {
        $result = [];
        $todayCarbon = Carbon::parse($today);
        
        // Obtener todas las recurrencias activas
        $recurrences = DB::select("
            SELECT 
                rt.id,
                rt.name,
                rt.transaction_description as description,
                rt.planned_amount as amount,
                rt.type,
                rt.frequency,
                rt.day_of_month,
                rt.start_date,
                rt.end_date,
                rt.last_generated_date,
                COALESCE(a.currency, 'EUR') as currency,
                a.name as account_name,
                c.name as category_name
            FROM recurring_templates rt
            LEFT JOIN accounts a ON rt.account_id = a.id
            LEFT JOIN categories c ON rt.category_id = c.id
            WHERE rt.user_id = ?
            AND rt.is_active = 1
            AND rt.deleted_at IS NULL
        ", [$this->userId]);
        
        foreach ($recurrences as $rec) {
            // Calcular la fecha de la última ejecución esperada
            $expectedDate = $this->getLastExpectedExecutionDate($rec, $todayCarbon);
            
            if (!$expectedDate) {
                continue;
            }
            
            // Verificar si ya existe una transacción para esta recurrencia en esa fecha
            // Buscamos por descripción similar y fecha cercana (±2 días)
            $existingTransaction = DB::selectOne("
                SELECT id FROM transactions 
                WHERE user_id = ? 
                AND (description LIKE ? OR description LIKE ?)
                AND effective_date BETWEEN DATE_SUB(?, INTERVAL 2 DAY) AND DATE_ADD(?, INTERVAL 2 DAY)
                AND deleted_at IS NULL
                LIMIT 1
            ", [
                $this->userId, 
                '%' . $rec->name . '%',
                '%' . ($rec->description ?? '') . '%',
                $expectedDate->format('Y-m-d'),
                $expectedDate->format('Y-m-d')
            ]);
            
            // Si no existe transacción y la fecha esperada es anterior a hoy, está vencida
            if (!$existingTransaction && $expectedDate->lt($todayCarbon)) {
                $daysOverdue = abs($expectedDate->diffInDays($todayCarbon));
                
                $result[] = [
                    'id' => $rec->id,
                    'description' => $rec->name,
                    'amount' => round(abs($rec->amount), 2),
                    'currency' => $rec->currency,
                    'due_date' => $expectedDate->format('Y-m-d'),
                    'days_overdue' => (int) $daysOverdue,
                    'source_type' => 'recurring_overdue',
                    'type' => $rec->type,
                    'status' => 'not_executed',
                    'account' => $rec->account_name,
                    'category' => $rec->category_name,
                ];
            }
        }
        
        return $result;
    }

    /**
     * Calcula la fecha de la última ejecución esperada para una recurrencia
     */
    private function getLastExpectedExecutionDate($recurrence, $today)
    {
        $startDate = Carbon::parse($recurrence->start_date);
        
        // Si aún no ha llegado la fecha de inicio, no hay ejecución esperada
        if ($startDate->gt($today)) {
            return null;
        }
        
        // Si tiene fecha de fin y ya pasó, usar la fecha de fin
        $endDate = $recurrence->end_date ? Carbon::parse($recurrence->end_date) : null;
        $referenceDate = ($endDate && $endDate->lt($today)) ? $endDate : $today;
        
        // Calcular la fecha esperada según la frecuencia
        switch ($recurrence->frequency) {
            case 'monthly':
                $dayOfMonth = $recurrence->day_of_month ?? $startDate->day;
                $expectedDate = $referenceDate->copy()->day(min($dayOfMonth, $referenceDate->daysInMonth));
                
                // Si la fecha calculada es posterior a hoy, retroceder un mes
                if ($expectedDate->gt($today)) {
                    $expectedDate->subMonth();
                    $expectedDate->day = min($dayOfMonth, $expectedDate->daysInMonth);
                }
                return $expectedDate;
                
            case 'weekly':
                $dayOfWeek = $startDate->dayOfWeek;
                $expectedDate = $referenceDate->copy()->startOfWeek()->addDays($dayOfWeek);
                if ($expectedDate->gt($today)) {
                    $expectedDate->subWeek();
                }
                return $expectedDate;
                
            case 'biweekly':
                $dayOfWeek = $startDate->dayOfWeek;
                $weeksSinceStart = $startDate->diffInWeeks($referenceDate);
                $biweeklyPeriods = floor($weeksSinceStart / 2);
                $expectedDate = $startDate->copy()->addWeeks($biweeklyPeriods * 2);
                if ($expectedDate->gt($today)) {
                    $expectedDate->subWeeks(2);
                }
                return $expectedDate;
                
            case 'quarterly':
                $dayOfMonth = $recurrence->day_of_month ?? $startDate->day;
                $quarterMonth = floor(($referenceDate->month - 1) / 3) * 3 + 1;
                $expectedDate = $referenceDate->copy()->month($quarterMonth)->day(min($dayOfMonth, Carbon::create($referenceDate->year, $quarterMonth, 1)->daysInMonth));
                if ($expectedDate->gt($today)) {
                    $expectedDate->subMonths(3);
                }
                return $expectedDate;
                
            case 'annual':
                $expectedDate = Carbon::create($referenceDate->year, $startDate->month, min($startDate->day, Carbon::create($referenceDate->year, $startDate->month, 1)->daysInMonth));
                if ($expectedDate->gt($today)) {
                    $expectedDate->subYear();
                }
                return $expectedDate;
                
            default:
                return null;
        }
    }
}
