<?php

namespace App\Http\Controllers;

use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Response;
use App\Custom\SQLConnection;
use Illuminate\Http\Request;
use App\Custom\Misc;
use Carbon\Carbon;
use Carbon\CarbonPeriod;
use DateTime;
use DB;

class DashboardJSONController extends Controller
{
  
    //
    public static function generarTodosLosJsons() {

        $carpeta_json = env("API_DASHBOARD_JSON_DIR");
        $productos = self::obtenerProductos();
        $clientes = self::obtenerClientes();
        $meses = self::Meses();
        $diasPorMesArray = self::Dias(); // Esto devuelve ['YYYY-MM' => ['YYYY-MM-DD', ...]]
        $cacheConsultas = [];
        if (!file_exists($carpeta_json)) {
            mkdir($carpeta_json, 0777, true);
        }
    
        // Unificar todos los rangos de fecha (meses y días individuales) en una sola lista
        $todosLosRangosTemporales = [];
        // foreach ($meses as $mes) {
        //     $todosLosRangosTemporales[] = $mes; // Ej: "2024-03"
        // }
        foreach ($diasPorMesArray as $mesKey => $diasEnMes) {
            foreach ($diasEnMes as $dia) {
                $todosLosRangosTemporales[] = $dia; // Ej: "2024-03-15"
            }
        }
        // Opcional: eliminar duplicados si un mes podría aparecer como un día (improbable con tu lógica actual)
        // $todosLosRangosTemporales = array_unique($todosLosRangosTemporales);
	    $todosLosRangosTemporales = [
            //'2026-01-19',
            // '2026-01',
            // '2026-01-01',
            // '2026-01-02',
            // '2026-01-03',
            // '2026-01-04',
            // '2026-01-05',
            // '2026-01-06',
            // '2026-01-07',
            // '2026-01-08',
            // '2026-01-09',
            // '2026-01-10',
            // '2026-01-11',
            // '2026-01-12',
            // '2026-01-13',
            // '2026-01-14',
            // '2026-01-15',
            // '2026-01-16',
            // '2026-01-17',
            // '2026-01-18',
            // '2026-01-19',
            // '2026-01-20',
            // '2026-01-21',
            // '2026-01-22',
            // '2026-01-23',
            // '2026-01-24',
            // '2026-01-25',
            // '2026-01-26',
            // '2026-01-27'
            //'2025-10-10',
            //'2025-10-11',
            //'2025-10-12',
            //'2025-10-13',
            //'2025-10-14',
            //'2025-10-15',
            //'2025-10-16',
            //'2025-10-17',
            //'2025-10-18',
            //'2025-10-19',
            //'2025-10-20',
            //'2025-10-21',
            //'2025-10-22',
            //'2025-10-23'
        ];    
        // Iterar sobre cada rango temporal (sea mes o día)
        foreach ($todosLosRangosTemporales as $rangoTemporal) {
            // 1. Todos los productos, Todos los clientes
                self::procesarConsulta(null, null, $rangoTemporal, $cacheConsultas);
            //dd($productos);
            // 2. Por cada producto (con todos los clientes)
            foreach ($productos as $producto) {
                self::procesarConsulta($producto->producto, null, $rangoTemporal, $cacheConsultas);
            }
    
            // 3. Por cada cliente (con todos los productos)
            foreach ($clientes as $cliente) {
                self::procesarConsulta(null, $cliente->cliente, $rangoTemporal, $cacheConsultas);
            }
    
            // 4. Por cada producto Y cada cliente
            foreach ($productos as $producto) {
                foreach ($clientes as $cliente) {
                    self::procesarConsulta($producto->producto, $cliente->cliente, $rangoTemporal, $cacheConsultas);
                }
            }            
        }

        return Response::json(['error' => 0,'mensaje' => 'JSON generados','fechas' => $todosLosRangosTemporales,'archivos' => $cacheConsultas]);

    }

    public static function generarJsonAyer() {

        $carpeta_json = env("API_DASHBOARD_JSON_DIR");
        $productos = self::obtenerProductos();
        $clientes = self::obtenerClientes();
        $meses = self::Meses();
        $diasPorMesArray = self::Dias(); // Esto devuelve ['YYYY-MM' => ['YYYY-MM-DD', ...]]
        $cacheConsultas = [];
        if (!file_exists($carpeta_json)) {
            mkdir($carpeta_json, 0777, true);
        }
        //
        $dt = new Carbon('yesterday');
        $rango_fechas = [];
        $rango_fechas[] = $dt->format('Y-m-d');
        $rango_fechas[] = $dt->format('Y-m');
//$rango_fechas2 = ['2026-08-16'];


        //
        foreach( $rango_fechas as $fecha ){
            // 1. Todos los productos, Todos los clientes
            self::procesarConsulta(null, null, $fecha, $cacheConsultas);
            //dd($productos);
            // 2. Por cada producto (con todos los clientes)
            foreach ($productos as $producto) {
                self::procesarConsulta($producto->producto, null, $fecha, $cacheConsultas);
            }

            // 3. Por cada cliente (con todos los productos)
            foreach ($clientes as $cliente) {
                self::procesarConsulta(null, $cliente->cliente, $fecha, $cacheConsultas);
            }

            // 4. Por cada producto Y cada cliente
            foreach ($productos as $producto) {
                foreach ($clientes as $cliente) {
                    self::procesarConsulta($producto->producto, $cliente->cliente, $fecha, $cacheConsultas);
                }
            }
        }
        //
        return Response::json(['error' => 0,'mensaje' => 'JSON generados','fechas' => $rango_fechas,'archivos' => $cacheConsultas]);                  
    }
    
    
    public static function generarClaveCache($productoId, $clienteId, $rangoFecha): string {
        $p = $productoId ?? 'todos';
        $c = $clienteId ?? 'todos';
        // $rangoFecha ya es un string como "2023-12" o "2023-12-01"
        return "prod_{$p}_cli_{$c}_fecha_{$rangoFecha}";
    }
    
    public static function generarNombreArchivo($productoId, $clienteId, $rangoFecha): string {
        $pNombre = $productoId ? "producto-{$productoId}" : "producto-todos";
        $cNombre = $clienteId ? "cliente-{$clienteId}" : "cliente-todos";
        // $rangoFecha es "YYYY-MM" o "YYYY-MM-DD"
        $fechaNombre = 'fecha-'.str_replace('-', '_', $rangoFecha);
        return "{$pNombre}_{$cNombre}_{$fechaNombre}";
    }
    
    // public static function procesarConsulta($productoId, $clienteId, $rangoFecha, &$cache): void {
    //     $clave = self::generarClaveCache($productoId, $clienteId, $rangoFecha);
        
    //     if (isset($cache[$clave])) {
    //         // echo "Cache HIT para: $clave\n"; // Para depuración
    //         return; 
    //     }
        
    //     $db = DB::connection('dbtest'); 
                
    //     $resultado = [

    //     ];
    
    //     $cache[$clave] = true;
    
    //     // $rangoFecha ya es un string, no un array con 'nombre'
    //     $nombreArchivo = self::generarNombreArchivo($productoId, $clienteId, $rangoFecha);
    //     $ruta = "json_generados/$nombreArchivo.json";
    //     file_put_contents($ruta, json_encode($resultado, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE));
    //     // echo "Generado: $ruta\n"; // Para depuración
    // }

    public static function procesarConsulta($productoId, $clienteId, $rangoFecha, &$cache): void {
        $clave = self::generarClaveCache($productoId, $clienteId, $rangoFecha);
        
        if (isset($cache[$clave])) {
            return; 
        }

        /* $nombreArchivo = self::generarNombreArchivo($productoId, $clienteId, $rangoFecha); //dd($nombreArchivo);
        $directorio = env('API_DASHBOARD_JSON_DIR');
        $ruta = "$directorio/$nombreArchivo.json";
        if (!File::isDirectory($directorio)) {
            File::makeDirectory($directorio, 0755, true, true);
        }
        if ( file_exists($ruta) ){
            return;
        }   */ 

        $directorioBase = env('API_DASHBOARD_JSON_DIR');

        $pNombre = $productoId ? "{$productoId}" : "all";
        //Logica para que el ID de clientes tenga 12 digitos completando con ceros a la izquierda
        $cNombre = Misc::formatearClienteId($clienteId);
        
        // Determinar la ruta según el tipo de fecha
        $rutaCarpeta = Misc::generarRutaCarpeta($rangoFecha, $clienteId, $productoId, $directorioBase);
        //dd($rutaCarpeta);

        // Crear carpetas si no existen
        if (!file_exists($rutaCarpeta)) {
            mkdir($rutaCarpeta, 0777, true); // `true` crea carpetas anidadas
        }

        $nombreArchivo = Misc::generarNombreArchivo($productoId, $clienteId, $rangoFecha);
        $rutaArchivo = "$rutaCarpeta/$nombreArchivo.json";
        // ----- DEFINICIÓN DE PERIODO Y MES BASADO EN RANGOFECHA -----
        // $rangoFecha será YYYY-MM-DD para día o YYYY-MM para mes.
        //  $clienteId = '';+
        // $productoId = '';
        //    $rangoFecha = '2025-05-14';
        //    $periodoDeterminado = '';           
        //    $mesDeterminado = null; // Número del mes (1-12)
        //    $diaEspecificoParaSp = null; // Fecha YYYY-MM-DD para diario, o NULL para mensual
        $diaDeterminado = NULL;
        if (strlen($rangoFecha) > 7) { // Formato YYYY-MM-DD (diario)
            $periodoDeterminado = 'diario';
            try {
                $fechaObj = Carbon::createFromFormat('Y-m-d', $rangoFecha);
                $mesDeterminado = $fechaObj->month; 
                $diaDeterminado = $fechaObj->format('Y-m-d');
                $diaEspecificoParaSp = $rangoFecha; // La fecha completa para el SP
            } catch (\Exception $e) {
                error_log("Error al parsear fecha diaria para SP: $rangoFecha - " . $e->getMessage());
                // Considerar qué hacer si la fecha es inválida. ¿Continuar con nulls? ¿Lanzar excepción?
            }
        } else { // Formato YYYY-MM (mensual)
            $periodoDeterminado = 'mensual';
            try {
                // Para SPs que esperan un mes numérico
                $mesDeterminado = Carbon::createFromFormat('Y-m', $rangoFecha)->month; 
                // $diaEspecificoParaSp permanece null
            } catch (\Exception $e) {
                error_log("Error al parsear fecha mensual para SP: $rangoFecha - " . $e->getMessage());
            }
        }
        
        // --- PARÁMETROS PARA STORE PROCEDURES ---
        // Todos los SPs (excepto los que usan 'BackendDashboardApi' como wrapper)
        // tomarán: [$periodoDeterminado, $mesDeterminado, $productoId, $clienteId, $diaEspecificoParaSp]

        // Los SPs que se llaman vía 'BackendDashboardApi' como primer parámetro:
        // El array de parámetros para ellos será: [$periodoDeterminado, $mesDeterminado, $productoId, $clienteId, $diaEspecificoParaSp]
        // y luego se les antepone el nombre de la acción.
        
        $paramsParaSpDirectos = [$periodoDeterminado, $mesDeterminado, $productoId, $clienteId, $diaDeterminado];
        
        // Para los SPs envueltos por BackendDashboardApi, la acción va primero en el array de SQLConnection
        $paramsPayloadParaBackendApi = [$periodoDeterminado, $mesDeterminado, $productoId, $clienteId , $diaDeterminado];

        //dd($paramsPayloadParaBackendApi);
        // ----- LLAMADAS A STORE PROCEDURES -----

        $rawSolicitudesTotales = SQLConnection::backendStoreProcedure('backendDashboardApi', array_merge(['GET_TOTAL_CONSULTAS'], $paramsParaSpDirectos));
        $solicitudesTotales = $rawSolicitudesTotales[0] ?? null;

        $rawTiempoPromedioResp = SQLConnection::backendStoreProcedure('backendDashboardApi', array_merge(['GET_TIEMPO_PROMEDIO'], $paramsPayloadParaBackendApi));
        $tiempoPromedioResp = $rawTiempoPromedioResp[0] ?? null;
        
        $rawTiempoMayorMenor = SQLConnection::backendStoreProcedure('backendDashboardApi', array_merge(['GET_TIEMPO_MAYOR_MENOR'], $paramsPayloadParaBackendApi));
        $tiempoMayorMenor = $rawTiempoMayorMenor[0] ?? null;

        $rawPromedioConsumoApi = SQLConnection::backendStoreProcedure('backendDashboardApi', array_merge(['GET_PROMEDIO_CONSUM_PROD'], $paramsPayloadParaBackendApi));
        $promedioConsumoApi = $rawPromedioConsumoApi[0] ?? null;
                    
        $rawPromedioConsumoCliente = SQLConnection::backendStoreProcedure('backendDashboardApi', array_merge(['GET_PROMEDIO_CONSUM_CLIENT'], $paramsPayloadParaBackendApi));
        $promedioConsumoCliente = $rawPromedioConsumoCliente[0] ?? null;

        $totalConsultasClienteGraf = SQLConnection::backendStoreProcedure('backendDashboardApi', array_merge(['GET_CONSULTAS_CLIENTE_GRAF'], $paramsPayloadParaBackendApi));
        
        $tiempoProce = SQLConnection::backendStoreProcedure('api_promedioTiempoProcesamiento', $paramsParaSpDirectos);
        $tiemposDetallados = [];

        //Paso $tiempoProce de array a colección, esto hace que sea mas facil agregar contenidos al json en caso de cambiar SP
        foreach ($tiempoProce as $item) {
            // Normalizamos el nombre del proveedor como clave para que coincida con clases de la blade
            $key = strtolower(str_replace(' ', '_', $item->proveedor ?? 'global'));

            // Armamos el array base
            $tiemposDetallados[$key] = [
                'duracion' => $item->duracion !== null ? (float) $item->duracion : null,
            ];

            // Se pasa nombres solo a primera key porque es la unica que los tendria
            if ($key === 'promedio_total_apis') {
                $tiemposDetallados[$key]['nombre_cliente'] = $item->nombre_cliente ?? null;
                $tiemposDetallados[$key]['nombre_producto'] = $item->nombre_producto ?? null;
            } else {
                $tiemposDetallados[$key]['proveedor'] = $item->proveedor;
            }
        }
    
        $totalConsultasProductGraf = SQLConnection::backendStoreProcedure('backendDashboardApi', array_merge(['GET_CONSULTAS_PRODUCT_GRAF'], $paramsPayloadParaBackendApi));
        
        $dataCodigoRespuesta = SQLConnection::backendStoreProcedure('backendDashboardApi', array_merge(['GET_CODIGO_RESP_DATA'], $paramsPayloadParaBackendApi));
    
        $tiempoPromedioProduct = SQLConnection::backendStoreProcedure('backendDashboardApi', array_merge(['GET_TIEMPO_PRODUCT'], $paramsPayloadParaBackendApi));
    
        $rangoTiempos = SQLConnection::backendStoreProcedure('backendDashboardApi', array_merge(['GET_RANGO_TIEMPO'], $paramsPayloadParaBackendApi));
    
        $apisMasErrores = SQLConnection::backendStoreProcedure('backendDashboardApi', array_merge(['GET_APIS_MAS_ERRORES'], $paramsPayloadParaBackendApi));
    
        $totalPromedioRespuestaGraf = SQLConnection::backendStoreProcedure('backendDashboardApi', array_merge(['GET_TOTAL_CONSULTAS_GRAF'], $paramsPayloadParaBackendApi));
    
        // $codResultAnual = SQLConnection::backendStoreProcedure('api_TasaErrores', $paramsParaSpDirectos);
        $codResultAnual = SQLConnection::backendStoreProcedure('backendDashboardApi', array_merge(['GET_COD_RESULTADOS_AN'],$paramsPayloadParaBackendApi));
            
        $tiempoPromedioAn = SQLConnection::backendStoreProcedure('backendDashboardApi', array_merge(['GET_TIEMPO_PROM_AN'],$paramsParaSpDirectos));


        // ----- PROCESAMIENTO DE DATOS PARA GRÁFICOS (Adaptado de tu código) -----
        
        // GRAFICO: Consumo por Cliente
        $nombre_cliente_graf = [];
        $consumo_total_graf = [];
        if(!empty($totalConsultasClienteGraf)){
            foreach ($totalConsultasClienteGraf as $dato) {
                $nombre_cliente_graf[] = $dato->nombre_cliente ?? 'N/A';
                $consumo_total_graf[]= (float)($dato->consumo_total ?? 0);
            }
        }

        // GRAFICO: Consumo por Producto/API
        $producto_graf = [];
        $total_registros_graf = [];
        if (!empty($totalConsultasProductGraf)) {
            foreach ($totalConsultasProductGraf as $dato) {
                $producto_graf[] = $dato->nombre_producto ?? 'N/A';
                $total_registros_graf[]= (float)($dato->total_registros ?? 0);
            }
        }

        // GRAFICO: Códigos de Respuesta
        $codigo_resultado_graf = [];
        $cantidad_Respuesta_graf = [];
        if (!empty($dataCodigoRespuesta)) {
            foreach ($dataCodigoRespuesta as $dato) {
                $codigo_resultado_graf[] = (string)($dato->codigo ?? 'N/A');
                $cantidad_Respuesta_graf[]= (float)($dato->cantidad ?? 0);
            }
        }
            
        // GRAFICO: APIs más Lentas
        $tiempo_api_resp_graf = [];
        $nombre_api_graf = [];
        if(!empty($tiempoPromedioProduct)){
            foreach ($tiempoPromedioProduct as $dato) {
                $tiempo_api_resp_graf[] = (float)($dato->promedio_duracion ?? 0.0);
                $nombre_api_graf[]= $dato->nombre_producto ?? 'N/A';
            }
        }
            
        // GRAFICO: Distribución de Tiempos
        $rango_tiempo_graf = [];
        $cantidad_Tiempo_graf = [];
        if(!empty($rangoTiempos)){
            foreach ($rangoTiempos as $dato) {
                $rango_tiempo_graf[] = $dato->rango_tiempo ?? 'N/A';
                $cantidad_Tiempo_graf[]= (float)($dato->cantidad ?? 0);
            }
        }
            
        // GRAFICO: APIs con más Errores
        $nombre_producto_errores_graf = [];
        $series_errores_graf = [];
        $tipos_error_keys = [ // Definir los tipos de error y las propiedades correspondientes del objeto $dato
            '307' => 'error_307', '306' => 'error_306', 'Internal Error' => 'internal_error',
            'Internal E' => 'internal_e', 'Error General' => 'error_general',
            'Error Producto' => 'error_producto', '400' => 'error_400'
        ];
        $errores_por_tipo_tmp = [];
        
        if(!empty($apisMasErrores)){
            foreach (array_keys($tipos_error_keys) as $key) {
                $errores_por_tipo_tmp[$key] = []; // Inicializa un array para cada tipo de error
            }
            foreach ($apisMasErrores as $dato) {
                $nombre_producto_errores_graf[] = $dato->nombre_producto ?? 'N/A';
                foreach ($tipos_error_keys as $displayName => $property) {
                $errores_por_tipo_tmp[$displayName][] = (int)($dato->{$property} ?? 0);
                }
            }
            foreach($errores_por_tipo_tmp as $name => $data_errores) { // Renombré $data a $data_errores
                if (!empty($data_errores)) { // Solo añadir series si tienen datos
                    $series_errores_graf[] = ['name' => $name, 'data' => $data_errores];
                }
            }
        }
        // Asegurarse que $nombre_producto_errores_graf tenga la misma longitud que los 'data' en $series_errores_graf
        // Si $apisMasErrores estaba vacío, $nombre_producto_errores_graf también lo estará, lo cual es correcto.
        
        // GRAFICO: Solicitudes OK/Error en el Periodo
        $categorias_periodo_graf = [];
        $aciertos_periodo_graf = [];
        $errores_periodo_graf = [];
        
        if(!empty($totalPromedioRespuestaGraf)){
            if($periodoDeterminado == 'mensual'){
                foreach ($totalPromedioRespuestaGraf as $dato) {
                    // Asumiendo que $dato->mes es YYYY-MM para mensual
                    $categorias_periodo_graf[] = Carbon::createFromFormat('Y-m', $dato->mes)->translatedFormat('M');
                    $aciertos_periodo_graf[] = (int)($dato->total_ok ?? 0);
                    $errores_periodo_graf[] = (int)($dato->total_error ?? 0) ;
                }
            }elseif($periodoDeterminado == 'diario'){
                foreach ($totalPromedioRespuestaGraf as $dato) {
                    // Asumiendo que $dato->mes es YYYY-MM-DD para diario en este SP
                    $fecha = Carbon::createFromFormat('Y-m-d', $dato->mes);
                    $categorias_periodo_graf[] = $fecha->translatedFormat('D d/m');
                    $aciertos_periodo_graf[] = (int)($dato->total_ok ?? 0);
                    $errores_periodo_graf[] = (int)($dato->total_error ?? 0) ;
                }
            }
        }
        //GRAFICO: TIEMPO DE PROCESAMIENTO
        $mes_tiempoPromedio_categorias = [];
        $promedio_duracion_data = [];
        if(!empty($tiempoPromedioAn)){
            if($periodoDeterminado == 'mensual'){
                foreach ($tiempoPromedioAn as $dato){
                    $mes_tiempoPromedio_categorias[] = Carbon::createFromFormat('Y-m', $dato->mes)->translatedFormat('M');
                    $promedio_duracion_data[] = (float)($dato->promedio_duracion ?? 0.0);
                }
            } elseif ($periodoDeterminado == 'diario'){
                foreach ($tiempoPromedioAn as $dato){
                    $mes_tiempoPromedio_categorias[] = Carbon::createFromFormat('Y-m-d', $dato->dia)->translatedFormat('D d/m');
                    $promedio_duracion_data[] = (float)($dato->promedio_duracion ?? 0.0);
                }
            }

        }
        // GRAFICO: Tasa de Errores por Código en el Periodo
        // $campoFecha es la columna en $codResultAnual que tiene la fecha ('dia' o 'mes')
        $campoFechaUsar = $periodoDeterminado == 'diario' ? 'dia' : 'mes';
        $agrupado_tasa_errores_graf = [];
        $series_tasa_errores_graf = [];

        if (!empty($codResultAnual)) {
            $agrupado_tasa_errores_graf = collect($codResultAnual)
                ->pluck($campoFechaUsar)
                ->map(function ($fecha) use ($periodoDeterminado) {
                    try {
                        return $periodoDeterminado == 'diario'
                            ? Carbon::createFromFormat('Y-m-d', $fecha)->translatedFormat('D d/m')
                            : Carbon::createFromFormat('Y-m', $fecha)->translatedFormat('M');
                    } catch (\Exception $e) { return 'FechaInv'; }
                })
                ->unique()
                ->values()
                ->all();
                
            $codigos = collect($codResultAnual)->pluck('codigo_resultado')->unique()->filter()->values()->all(); // filter() para quitar nulos/vacíos
            $erroresPorPeriodoYCodigo = [];
            foreach ($codigos as $codigo) {
                $erroresPorPeriodoYCodigo[(string)$codigo] = array_fill_keys($agrupado_tasa_errores_graf, 0);
            }
            
            foreach ($codResultAnual as $item) {
                try {
                    $fechaFormateada = $periodoDeterminado == 'diario'
                        ? Carbon::createFromFormat('Y-m-d', $item->{$campoFechaUsar})->translatedFormat('D d/m')
                        : Carbon::createFromFormat('Y-m', $item->{$campoFechaUsar})->translatedFormat('M');
                    $codigo = (string)($item->codigo_resultado ?? 'N/A');
                    if (isset($erroresPorPeriodoYCodigo[$codigo][$fechaFormateada])) {
                        $erroresPorPeriodoYCodigo[$codigo][$fechaFormateada] += (int)($item->total_error ?? 0);
                    }
                } catch (\Exception $e) { /* Omitir si la fecha es inválida */ }
            }
            foreach ($erroresPorPeriodoYCodigo as $codigo => $data_errores) { // Renombré $data a $data_errores
                $series_tasa_errores_graf[] = [
                    'name' => $codigo,
                    'data' => array_values($data_errores),
                ];
            }
        }

        // ----- METADATA -----
        $mesConsultadoStr = '';
        $subtitulo = '';
        Carbon::setLocale('es');

        if ($periodoDeterminado == 'diario') {
            try {
                $mesConsultadoStr = Carbon::createFromFormat('Y-m-d', $rangoFecha)->translatedFormat('j \d\e F');
            } catch (\Exception $e) { $mesConsultadoStr = "Día $rangoFecha"; }
            $subtitulo = 'ÚLTIMOS 15 DÍAS'; // Esto era fijo en tu código original, ajústalo si depende del rango real
        } else { // mensual
            try {
                $mesConsultadoStr = Carbon::createFromFormat('Y-m', $rangoFecha)->translatedFormat('F \d\e Y');
            } catch (\Exception $e) { $mesConsultadoStr = "Mes $rangoFecha"; }
            $subtitulo = 'ÚLTIMOS 12 MESES'; // Esto era fijo, ajústalo
        }
        // Si se filtra por producto o cliente, podrías añadirlo al subtitulo
        // if($productoId) $subtitulo .= " - Prod: $productoId";
        // if($clienteId) $subtitulo .= " - Cli: $clienteId";


        /**esto se va a borrar  */
            // --- INICIO DE DATOS HARDCODEADOS PARA EL HEATMAP --
                        $datosUptimeHeatmap = [
                            'monthName' => '',
                            'daysInMonth' => '',
                            'services' => [
                                [
                                    'name' => 'FIDELITAS',
                                    'overallUptime' => '',
                                    'dailyData' => ''
                                ],
                                [
                                    'name' => 'RENAPER',
                                    'overallUptime' => '',
                                    'dailyData' => ''
                                ],
                                [
                                    'name' => 'ARCA',
                                    'overallUptime' => '',
                                    'dailyData' => ''
                                ]
                            ]
                        ];
        // ----- CONSTRUIR EL JSON FINAL -----
        $resultado = [
            'metadata' => [
                'mesConsultado' => $mesConsultadoStr,
                'subtitulo' => $subtitulo,
                'periodo' => $periodoDeterminado,
            ],
            'cards' => [
                'solicitudes' => [
                    'total' => (int)($solicitudesTotales->cantidad_total ?? 0),
                    'ok' => (int)($solicitudesTotales->total_ok ?? 0),
                    'error' => (int)($solicitudesTotales->total_error ?? 0),
                ],
                'Ultrenaper' => '',
                'Ultarca' => '',
                'tiempoPromedioSegundos' => (float)($tiempoPromedioResp->segundos ?? 0.0), // Asumiendo que la propiedad es 'segundos'
                'tiemposExtremos' => [
                'min' => isset($tiempoMayorMenor->duracion_minima) ? (float)$tiempoMayorMenor->duracion_minima : null,
                'max' => isset($tiempoMayorMenor->duracion_maxima) ? (float)$tiempoMayorMenor->duracion_maxima : null,
                ],
                'consumoPromedio' => [
                    'porApi' => (float)($promedioConsumoApi->promedio_por_api ?? 0.0),
                    'porCliente' => (float)($promedioConsumoCliente->promedio_por_cliente ?? 0.0),
                ],
                'tiemposDetallados' => $tiempoProce ? $tiemposDetallados : null
        ],
        'graphs' => [
                'solicitudesPeriodo' => [
                'categories' => $categorias_periodo_graf,
                'series' => [
                    [ 'name' => 'Aciertos', 'data' => $aciertos_periodo_graf ],
                    [ 'name' => 'Errores', 'data' => $errores_periodo_graf ],
                ]
                ],
                'tasaAciertosErroresPie' => [ // Para #TortaPorcentaje
                    'seriesData' => [
                    ['name' => 'Aciertos', 'y' => (int)($solicitudesTotales->total_ok ?? 0) ],
                    ['name' => 'Errores',  'y' => (int)($solicitudesTotales->total_error ?? 0) ],
                    ]
                ],
                'consumoPorApi' => [ // Para #apisConsumo
                    'categories' => $producto_graf,
                    'series' => [ ['name' => 'Consumos', 'data' => $total_registros_graf ] ]
                ],
                'consumoPorCliente' => [ // Para #clientesConsumo
                    'categories' => $nombre_cliente_graf,
                    'series' => [ ['name' => 'Consumos', 'data' => $consumo_total_graf ] ]
                ],
                'tasaErroresPorCodigo' => [ // Para #TasadeErrores
                    'categories' => $agrupado_tasa_errores_graf,
                    'series' => $series_tasa_errores_graf,
                ],
                'apisConMasErrores' => [ // Para #ApimasErrores
                    'categories' => $nombre_producto_errores_graf, // Nombres de productos
                    'series' => $series_errores_graf, // Array de {name: 'TipoError', data: [...]}
                ],  
                // ----- TIEMPO PROCESAMIENTO GRAPH - CORRECTED STRUCTURE -----
                'tiempoProcesamiento' => [ // para #TiempoProcesamiento
                    'categories' => $mes_tiempoPromedio_categorias,
                    'series' => [
                        // The series should be an array of objects, each with 'name' and 'data'
                        ['name' => 'Tiempo Promedio (s)', 'data' => $promedio_duracion_data] 
                        // Adjust 'Tiempo Promedio (s)' if unit or name is different
                    ]
                ],
                    'codigosRespuesta' => [ // Para #CodigosResp
                        'categories' => $codigo_resultado_graf,
                        'series' => [ ['name' => 'Cantidad', 'data' => $cantidad_Respuesta_graf] ]
                    ],
                    'apisMasLentas' => [ // Para #ApismasLentas
                        'categories' => $nombre_api_graf,
                        'series' => [ ['name' => 'Segundos', 'data' => $tiempo_api_resp_graf] ]
                    ],
                    'distribucionTiempos' => [ // Para #DistriProce
                        'categories' => $rango_tiempo_graf,
                        'series' => [ ['name' => 'Cantidad', 'data' => $cantidad_Tiempo_graf] ]
                    ],
                'uptimeHeatmap' => $datosUptimeHeatmap
            ]
        ];
        
        $cache[$clave] = true;
        file_put_contents($rutaArchivo, json_encode($resultado, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE));
    }

    public static function procesarConsultaOrg($productoId, $clienteId, $rangoFecha, &$cache): void {
        // dd('llego');
        $clave = self::generarClaveCache($productoId, $clienteId, $rangoFecha);
        
        if (isset($cache[$clave])) {
            return; 
        }
        
        $nombreArchivo = self::generarNombreArchivo($productoId, $clienteId, $rangoFecha); //dd($nombreArchivo);
        $directorio = env('API_DASHBOARD_JSON_DIR');
        $ruta = "$directorio/$nombreArchivo.json";
        if (!File::isDirectory($directorio)) {
            File::makeDirectory($directorio, 0755, true, true);
        }
        if ( file_exists($ruta) ){
            return;
        }
        // ----- DEFINICIÓN DE PERIODO Y MES BASADO EN RANGOFECHA -----
        // $rangoFecha será YYYY-MM-DD para día o YYYY-MM para mes.
        //  $clienteId = '';+
        // $productoId = '';
        //    $rangoFecha = '2025-05-14';
        //    $periodoDeterminado = '';           
        //    $mesDeterminado = null; // Número del mes (1-12)
        //    $diaEspecificoParaSp = null; // Fecha YYYY-MM-DD para diario, o NULL para mensual
   
        if (strlen($rangoFecha) > 7) { // Formato YYYY-MM-DD (diario)
            $periodoDeterminado = 'diario';
            try {
                $fechaObj = Carbon::createFromFormat('Y-m-d', $rangoFecha);
                $mesDeterminado = $fechaObj->month; 
                $diaEspecificoParaSp = $rangoFecha; // La fecha completa para el SP
            } catch (\Exception $e) {
                error_log("Error al parsear fecha diaria para SP: $rangoFecha - " . $e->getMessage());
                // Considerar qué hacer si la fecha es inválida. ¿Continuar con nulls? ¿Lanzar excepción?
            }
        } else { // Formato YYYY-MM (mensual)
            $periodoDeterminado = 'mensual';
            try {
                // Para SPs que esperan un mes numérico
                $mesDeterminado = Carbon::createFromFormat('Y-m', $rangoFecha)->month; 
                // $diaEspecificoParaSp permanece null
            } catch (\Exception $e) {
                error_log("Error al parsear fecha mensual para SP: $rangoFecha - " . $e->getMessage());
            }
        }
           
        // --- PARÁMETROS PARA STORE PROCEDURES ---
        // Todos los SPs (excepto los que usan 'BackendDashboardApi' como wrapper)
        // tomarán: [$periodoDeterminado, $mesDeterminado, $productoId, $clienteId, $diaEspecificoParaSp]

        // Los SPs que se llaman vía 'BackendDashboardApi' como primer parámetro:
        // El array de parámetros para ellos será: [$periodoDeterminado, $mesDeterminado, $productoId, $clienteId, $diaEspecificoParaSp]
        // y luego se les antepone el nombre de la acción.
        
        $paramsParaSpDirectos = [$periodoDeterminado, $mesDeterminado, $productoId, $clienteId];
        
        // Para los SPs envueltos por BackendDashboardApi, la acción va primero en el array de SQLConnection
        $paramsPayloadParaBackendApi = [$periodoDeterminado, $mesDeterminado, $productoId, $clienteId];

        //dd($paramsPayloadParaBackendApi);
        // ----- LLAMADAS A STORE PROCEDURES -----

        $rawSolicitudesTotales = SQLConnection::backendStoreProcedure('backendDashboardApi_v2', array_merge(['GET_TOTAL_CONSULTAS'], $paramsParaSpDirectos));
        $solicitudesTotales = $rawSolicitudesTotales[0] ?? null;

        $rawTiempoPromedioResp = SQLConnection::backendStoreProcedure('backendDashboardApi_v2', array_merge(['GET_TIEMPO_PROMEDIO'], $paramsPayloadParaBackendApi));
        $tiempoPromedioResp = $rawTiempoPromedioResp[0] ?? null;
        
        $rawTiempoMayorMenor = SQLConnection::backendStoreProcedure('backendDashboardApi_v2', array_merge(['GET_TIEMPO_MAYOR_MENOR'], $paramsPayloadParaBackendApi));
        $tiempoMayorMenor = $rawTiempoMayorMenor[0] ?? null;

        $rawPromedioConsumoApi = SQLConnection::backendStoreProcedure('backendDashboardApi_v2', array_merge(['GET_PROMEDIO_CONSUM_PROD'], $paramsPayloadParaBackendApi));
        $promedioConsumoApi = $rawPromedioConsumoApi[0] ?? null;
                    
        $rawPromedioConsumoCliente = SQLConnection::backendStoreProcedure('backendDashboardApi_v2', array_merge(['GET_PROMEDIO_CONSUM_CLIENT'], $paramsPayloadParaBackendApi));
        $promedioConsumoCliente = $rawPromedioConsumoCliente[0] ?? null;

        $totalConsultasClienteGraf = SQLConnection::backendStoreProcedure('backendDashboardApi_v2', array_merge(['GET_CONSULTAS_CLIENTE_GRAF'], $paramsPayloadParaBackendApi));
        
        $rawTiempoProce = SQLConnection::backendStoreProcedure('backendDashboardApi_v2', array_merge(['GET_PROMEDIO_CONSUM_PROD'], $paramsParaSpDirectos));
        $tiempoProce = $rawTiempoProce[0] ?? null;
    
        $totalConsultasProductGraf = SQLConnection::backendStoreProcedure('backendDashboardApi_v2', array_merge(['GET_CONSULTAS_PRODUCT_GRAF'], $paramsPayloadParaBackendApi));
        
        $dataCodigoRespuesta = SQLConnection::backendStoreProcedure('backendDashboardApi_v2', array_merge(['GET_CODIGO_RESP_DATA'], $paramsPayloadParaBackendApi));
    
        $tiempoPromedioProduct = SQLConnection::backendStoreProcedure('backendDashboardApi_v2', array_merge(['GET_TIEMPO_PRODUCT'], $paramsPayloadParaBackendApi));
    
        $rangoTiempos = SQLConnection::backendStoreProcedure('backendDashboardApi_v2', array_merge(['GET_RANGO_TIEMPO'], $paramsPayloadParaBackendApi));
    
        $apisMasErrores = SQLConnection::backendStoreProcedure('backendDashboardApi_v2', array_merge(['GET_APIS_MAS_ERRORES'], $paramsPayloadParaBackendApi));
    
        $totalPromedioRespuestaGraf = SQLConnection::backendStoreProcedure('backendDashboardApi_v2', array_merge(['GET_TOTAL_CONSULTAS_GRAF'], $paramsPayloadParaBackendApi));
    
        // $codResultAnual = SQLConnection::backendStoreProcedure('api_TasaErrores', $paramsParaSpDirectos);
        $codResultAnual = SQLConnection::backendStoreProcedure('backendDashboardApi_v2', ['GET_COD_RESULTADOS_AN']);
        
        $tiempoPromedioAn = SQLConnection::backendStoreProcedure('backendDashboardApi_v2', array_merge(['GET_TIEMPO_PROM_AN']), $paramsParaSpDirectos);


        // ----- PROCESAMIENTO DE DATOS PARA GRÁFICOS (Adaptado de tu código) -----
        
        // GRAFICO: Consumo por Cliente
        $nombre_cliente_graf = [];
        $consumo_total_graf = [];
        if(!empty($totalConsultasClienteGraf)){
            foreach ($totalConsultasClienteGraf as $dato) {
                $nombre_cliente_graf[] = $dato->nombre_cliente ?? 'N/A';
                $consumo_total_graf[]= (float)($dato->consumo_total ?? 0);
            }
        }

        // GRAFICO: Consumo por Producto/API
        $producto_graf = [];
        $total_registros_graf = [];
        if (!empty($totalConsultasProductGraf)) {
            foreach ($totalConsultasProductGraf as $dato) {
                $producto_graf[] = $dato->nombre_producto ?? 'N/A';
                $total_registros_graf[]= (float)($dato->total_registros ?? 0);
            }
        }

        // GRAFICO: Códigos de Respuesta
        $codigo_resultado_graf = [];
        $cantidad_Respuesta_graf = [];
        if (!empty($dataCodigoRespuesta)) {
            foreach ($dataCodigoRespuesta as $dato) {
                $codigo_resultado_graf[] = (string)($dato->codigo ?? 'N/A');
                $cantidad_Respuesta_graf[]= (float)($dato->cantidad ?? 0);
            }
        }
            
        // GRAFICO: APIs más Lentas
        $tiempo_api_resp_graf = [];
        $nombre_api_graf = [];
        if(!empty($tiempoPromedioProduct)){
            foreach ($tiempoPromedioProduct as $dato) {
                $tiempo_api_resp_graf[] = (float)($dato->promedio_duracion ?? 0.0);
                $nombre_api_graf[]= $dato->nombre_producto ?? 'N/A';
            }
        }
            
        // GRAFICO: Distribución de Tiempos
        $rango_tiempo_graf = [];
        $cantidad_Tiempo_graf = [];
        if(!empty($rangoTiempos)){
            foreach ($rangoTiempos as $dato) {
                $rango_tiempo_graf[] = $dato->rango_tiempo ?? 'N/A';
                $cantidad_Tiempo_graf[]= (float)($dato->cantidad ?? 0);
            }
        }
            
        // GRAFICO: APIs con más Errores
        $nombre_producto_errores_graf = [];
        $series_errores_graf = [];
        $tipos_error_keys = [ // Definir los tipos de error y las propiedades correspondientes del objeto $dato
            '307' => 'error_307', '306' => 'error_306', 'Internal Error' => 'internal_error',
            'Internal E' => 'internal_e', 'Error General' => 'error_general',
            'Error Producto' => 'error_producto', '400' => 'error_400'
        ];
        $errores_por_tipo_tmp = [];
        
        if(!empty($apisMasErrores)){
            foreach (array_keys($tipos_error_keys) as $key) {
                $errores_por_tipo_tmp[$key] = []; // Inicializa un array para cada tipo de error
            }
            foreach ($apisMasErrores as $dato) {
                $nombre_producto_errores_graf[] = $dato->nombre_producto ?? 'N/A';
                foreach ($tipos_error_keys as $displayName => $property) {
                $errores_por_tipo_tmp[$displayName][] = (int)($dato->{$property} ?? 0);
                }
            }
            foreach($errores_por_tipo_tmp as $name => $data_errores) { // Renombré $data a $data_errores
                if (!empty($data_errores)) { // Solo añadir series si tienen datos
                    $series_errores_graf[] = ['name' => $name, 'data' => $data_errores];
                }
            }
        }
        // Asegurarse que $nombre_producto_errores_graf tenga la misma longitud que los 'data' en $series_errores_graf
        // Si $apisMasErrores estaba vacío, $nombre_producto_errores_graf también lo estará, lo cual es correcto.
        
        // GRAFICO: Solicitudes OK/Error en el Periodo
        $categorias_periodo_graf = [];
        $aciertos_periodo_graf = [];
        $errores_periodo_graf = [];
        
        if(!empty($totalPromedioRespuestaGraf)){
            if($periodoDeterminado == 'mensual'){
                foreach ($totalPromedioRespuestaGraf as $dato) {
                    // Asumiendo que $dato->mes es YYYY-MM para mensual
                    $categorias_periodo_graf[] = Carbon::createFromFormat('Y-m', $dato->mes)->translatedFormat('M');
                    $aciertos_periodo_graf[] = (int)($dato->total_ok ?? 0);
                    $errores_periodo_graf[] = (int)($dato->total_error ?? 0) ;
                }
            }elseif($periodoDeterminado == 'diario'){
                foreach ($totalPromedioRespuestaGraf as $dato) {
                    // Asumiendo que $dato->mes es YYYY-MM-DD para diario en este SP
                    $fecha = Carbon::createFromFormat('Y-m-d', $dato->mes);
                    $categorias_periodo_graf[] = $fecha->translatedFormat('D d/m');
                    $aciertos_periodo_graf[] = (int)($dato->total_ok ?? 0);
                    $errores_periodo_graf[] = (int)($dato->total_error ?? 0) ;
                }
            }
        }
        //GRAFICO: TIEMPO DE PROCESAMIENTO
        $mes_tiempoPromedio_categorias = [];
        $promedio_duracion_data = [];
        if(!empty($tiempoPromedioAn)){
            foreach ($tiempoPromedioAn as $dato){
                $mes_tiempoPromedio_categorias[] = Carbon::createFromFormat('Y-m', $dato->mes)->translatedFormat('M');
                $promedio_duracion_data[] = (float)($dato->promedio_duracion ?? 0.0);
            }

        }
        // GRAFICO: Tasa de Errores por Código en el Periodo
        // $campoFecha es la columna en $codResultAnual que tiene la fecha ('dia' o 'mes')
        $campoFechaUsar = $periodoDeterminado == 'diario' ? 'dia' : 'mes';
        $agrupado_tasa_errores_graf = [];
        $series_tasa_errores_graf = [];

        if (!empty($codResultAnual)) {
            $agrupado_tasa_errores_graf = collect($codResultAnual)
                ->pluck($campoFechaUsar)
                ->map(function ($fecha) use ($periodoDeterminado) {
                    try {
                        return $periodoDeterminado == 'diario'
                            ? Carbon::createFromFormat('Y-m-d', $fecha)->translatedFormat('D d/m')
                            : Carbon::createFromFormat('Y-m', $fecha)->translatedFormat('M');
                    } catch (\Exception $e) { return 'FechaInv'; }
                })
                ->unique()
                ->values()
                ->all();
                
            $codigos = collect($codResultAnual)->pluck('codigo_resultado')->unique()->filter()->values()->all(); // filter() para quitar nulos/vacíos
            $erroresPorPeriodoYCodigo = [];
            foreach ($codigos as $codigo) {
                $erroresPorPeriodoYCodigo[(string)$codigo] = array_fill_keys($agrupado_tasa_errores_graf, 0);
            }
            
            foreach ($codResultAnual as $item) {
                try {
                    $fechaFormateada = $periodoDeterminado == 'diario'
                        ? Carbon::createFromFormat('Y-m-d', $item->{$campoFechaUsar})->translatedFormat('D d/m')
                        : Carbon::createFromFormat('Y-m', $item->{$campoFechaUsar})->translatedFormat('M');
                    $codigo = (string)($item->codigo_resultado ?? 'N/A');
                    if (isset($erroresPorPeriodoYCodigo[$codigo][$fechaFormateada])) {
                        $erroresPorPeriodoYCodigo[$codigo][$fechaFormateada] += (int)($item->total_error ?? 0);
                    }
                } catch (\Exception $e) { /* Omitir si la fecha es inválida */ }
            }
            foreach ($erroresPorPeriodoYCodigo as $codigo => $data_errores) { // Renombré $data a $data_errores
                $series_tasa_errores_graf[] = [
                    'name' => $codigo,
                    'data' => array_values($data_errores),
                ];
            }
        }

        // ----- METADATA -----
        $mesConsultadoStr = '';
        $subtitulo = '';

        if ($periodoDeterminado == 'diario') {
            try {
                $mesConsultadoStr = Carbon::createFromFormat('Y-m-d', $rangoFecha)->translatedFormat('j \d\e F');
            } catch (\Exception $e) { $mesConsultadoStr = "Día $rangoFecha"; }
            $subtitulo = 'ÚLTIMOS 15 DÍAS'; // Esto era fijo en tu código original, ajústalo si depende del rango real
        } else { // mensual
            try {
                $mesConsultadoStr = Carbon::createFromFormat('Y-m', $rangoFecha)->translatedFormat('F \d\e Y');
            } catch (\Exception $e) { $mesConsultadoStr = "Mes $rangoFecha"; }
            $subtitulo = 'ÚLTIMOS 12 MESES'; // Esto era fijo, ajústalo
        }
        // Si se filtra por producto o cliente, podrías añadirlo al subtitulo
        // if($productoId) $subtitulo .= " - Prod: $productoId";
        // if($clienteId) $subtitulo .= " - Cli: $clienteId";


        /**esto se va a borrar  */
        // --- INICIO DE DATOS HARDCODEADOS PARA EL HEATMAP --
        $datosUptimeHeatmap = [
            'monthName' => '',
            'daysInMonth' => '',
            'services' => [
                [
                    'name' => 'FIDELITAS',
                    'overallUptime' => '',
                    'dailyData' => ''
                ],
                [
                    'name' => 'RENAPER',
                    'overallUptime' => '',
                    'dailyData' => ''
                ],
                [
                    'name' => 'ARCA',
                    'overallUptime' => '',
                    'dailyData' => ''
                ]
            ]
        ];
        // ----- CONSTRUIR EL JSON FINAL -----
        $resultado = [
            'metadata' => [
                'mesConsultado' => $mesConsultadoStr,
                'subtitulo' => $subtitulo,
                'periodo' => $periodoDeterminado,
            ],
            'cards' => [
                'solicitudes' => [
                    'total' => (int)($solicitudesTotales->cantidad_total ?? 0),
                    'ok' => (int)($solicitudesTotales->total_ok ?? 0),
                    'error' => (int)($solicitudesTotales->total_error ?? 0),
                ],
                'Ultrenaper' => '',
                'Ultarca' => '',
                'tiempoPromedioSegundos' => (float)($tiempoPromedioResp->segundos ?? 0.0), // Asumiendo que la propiedad es 'segundos'
                'tiemposExtremos' => [
                'min' => isset($tiempoMayorMenor->duracion_minima) ? (float)$tiempoMayorMenor->duracion_minima : null,
                'max' => isset($tiempoMayorMenor->duracion_maxima) ? (float)$tiempoMayorMenor->duracion_maxima : null,
                ],
                'consumoPromedio' => [
                    'porApi' => (float)($promedioConsumoApi->promedio_por_api ?? 0.0),
                    'porCliente' => (float)($promedioConsumoCliente->promedio_por_cliente ?? 0.0),
                ],
                'tiemposDetallados' => $tiempoProce ? [ // Solo si $tiempoProce no es null
                    'nombre_producto' => $tiempoProce->nombre_producto ?? null,
                    'nombre_cliente' => $tiempoProce->nombre_cliente ?? null,
                    'promedio_duracion' => (float)($tiempoProce->promedio_duracion ?? 0.0),
                    'promedio_duracion_renaper' => (float)($tiempoProce->promedio_duracion_renaper ?? 0.0),
                    'promedio_duracion_arca' => (float)($tiempoProce->promedio_duracion_arca ?? 0.0),
                    'promedio_duracion_worlddsys' => (float)($tiempoProce->promedio_duracion_worlddsys ?? 0.0),
                ] : null,
        ],
        'graphs' => [
                'solicitudesPeriodo' => [
                'categories' => $categorias_periodo_graf,
                'series' => [
                    [ 'name' => 'Aciertos', 'data' => $aciertos_periodo_graf ],
                    [ 'name' => 'Errores', 'data' => $errores_periodo_graf ],
                ]
                ],
                'tasaAciertosErroresPie' => [ // Para #TortaPorcentaje
                    'seriesData' => [
                    ['name' => 'Aciertos', 'y' => (int)($solicitudesTotales->total_ok ?? 0) ],
                    ['name' => 'Errores',  'y' => (int)($solicitudesTotales->total_error ?? 0) ],
                    ]
                ],
                'consumoPorApi' => [ // Para #apisConsumo
                    'categories' => $producto_graf,
                    'series' => [ ['name' => 'Consumos', 'data' => $total_registros_graf ] ]
                ],
                'consumoPorCliente' => [ // Para #clientesConsumo
                    'categories' => $nombre_cliente_graf,
                    'series' => [ ['name' => 'Consumos', 'data' => $consumo_total_graf ] ]
                ],
                'tasaErroresPorCodigo' => [ // Para #TasadeErrores
                    'categories' => $agrupado_tasa_errores_graf,
                    'series' => $series_tasa_errores_graf,
                ],
                'apisConMasErrores' => [ // Para #ApimasErrores
                    'categories' => $nombre_producto_errores_graf, // Nombres de productos
                    'series' => $series_errores_graf, // Array de {name: 'TipoError', data: [...]}
                ],  
                // ----- TIEMPO PROCESAMIENTO GRAPH - CORRECTED STRUCTURE -----
                'tiempoProcesamiento' => [ // para #TiempoProcesamiento
                    'categories' => $mes_tiempoPromedio_categorias,
                    'series' => [
                        // The series should be an array of objects, each with 'name' and 'data'
                        ['name' => 'Tiempo Promedio (s)', 'data' => $promedio_duracion_data] 
                        // Adjust 'Tiempo Promedio (s)' if unit or name is different
                    ]
                ],
                    'codigosRespuesta' => [ // Para #CodigosResp
                        'categories' => $codigo_resultado_graf,
                        'series' => [ ['name' => 'Cantidad', 'data' => $cantidad_Respuesta_graf] ]
                    ],
                    'apisMasLentas' => [ // Para #ApismasLentas
                        'categories' => $nombre_api_graf,
                        'series' => [ ['name' => 'Segundos', 'data' => $tiempo_api_resp_graf] ]
                    ],
                    'distribucionTiempos' => [ // Para #DistriProce
                        'categories' => $rango_tiempo_graf,
                        'series' => [ ['name' => 'Cantidad', 'data' => $cantidad_Tiempo_graf] ]
                    ],
                'uptimeHeatmap' => $datosUptimeHeatmap
            ]
        ];
           
        $cache[$clave] = true;
        file_put_contents($ruta, json_encode($resultado, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE));
    }

    // Funciones para los array de los filtros
    public static function obtenerProductos(){
        return SQLConnection::backendStoreProcedure('getProductosDashboardApi',[]);
    }

    public static function obtenerClientes(){
        return SQLConnection::backendStoreProcedure('getClientesDashboardApi',[]);;
    }

    public static function Meses(){
        $meses = [];
        $fechaActual = new DateTime();

        // Meses desde el actual hasta enero del mismo año
        for ($i = 0; $i < $fechaActual->format('n'); $i++) {
            $mes = (clone $fechaActual)->modify("-$i months");
            $meses[] = $mes->format('Y-m');
        }

        // 12 meses del año pasado
        $anioAnterior = (int)$fechaActual->format('Y') - 1;
        for ($i = 11; $i >= 0; $i--) {
            $mes = DateTime::createFromFormat('Y-n', "$anioAnterior-" . ($i + 1));
            $meses[] = $mes->format('Y-m');
        }

        return $meses;
    }

    public static function Dias(){
        $diasPorMes = [];
        $fechaInicio = new DateTime(date('Y') - 1 . '-01-01'); // 1 de enero del año pasado
        $fechaFin = new DateTime(); // Hoy

        while ($fechaInicio <= $fechaFin) {
            $claveMes = $fechaInicio->format('Y-m'); // ej: 2024-03
            $dia = $fechaInicio->format('Y-m-d');

            if (!isset($diasPorMes[$claveMes])) {
                $diasPorMes[$claveMes] = [];
            }

            $diasPorMes[$claveMes][] = $dia;

            $fechaInicio->modify('+1 day');
        }

        // Orden descendente por mes
        krsort($diasPorMes);

        return $diasPorMes;
    }

    public static function generarJsonFechas(?string $desde = null, ?string $hasta = null)
    { 
        // Fallback: parámetros explícitos → HTTP → defaults
        $desde = $desde ?? Request::input('desde');
        $hasta = $hasta ?? Request::input('hasta');

        // Si no vino ninguna, usar AYER
        if (empty($desde) && empty($hasta)) {
            $ayer  = Carbon::yesterday()->toDateString(); // YYYY-MM-DD
            $desde = $ayer;
            $hasta = $ayer;
        }
        // Si vino solo una, igualar la otra
        if (empty($desde) && !empty($hasta)) { $desde = $hasta; }
        if (!empty($desde) && empty($hasta)) { $hasta = $desde; }

        if (!$desde || !$hasta) {
            throw new \InvalidArgumentException("Faltan fechas: 'desde' y/o 'hasta'.");
        }

        // Detecto formato de entrada (YYYY-MM-DD) o (YYYY-MM)
        $fmtDia = preg_match('/^\d{4}-\d{2}-\d{2}$/', $desde) && preg_match('/^\d{4}-\d{2}-\d{2}$/', $hasta);
        $fmtMes = preg_match('/^\d{4}-\d{2}$/',     $desde) && preg_match('/^\d{4}-\d{2}$/',     $hasta);

        if (!($fmtDia || $fmtMes)) {
            throw new \InvalidArgumentException("Formato inválido. Use YYYY-MM-DD/ YYYY-MM (coherentes ambos).");
        }

        // Normalizo start/end según formato
        if ($fmtMes) {
            $start = Carbon::parse($desde.'-01')->startOfMonth();
            $end   = Carbon::parse($hasta.'-01')->endOfMonth();
        } else {
            $start = Carbon::parse($desde)->startOfDay();
            $end   = Carbon::parse($hasta)->endOfDay();
        }

        $carpeta_json = env("API_DASHBOARD_JSON_DIR");
        $productos    = self::obtenerProductos();
        $clientes     = self::obtenerClientes();

        $cacheConsultas = [];

        // --- 1) DÍAS del rango
        $dias = [];
        $periodDias = CarbonPeriod::create($start->copy()->startOfDay(), '1 day', $end->copy()->startOfDay());
        foreach ($periodDias as $d) {
            $dias[] = $d->format('Y-m-d');
        }

        // --- 2) MESES del rango
        $meses = [];
        $mStart = $start->copy()->startOfMonth();
        $mEnd   = $end->copy()->startOfMonth();
        $periodMeses = CarbonPeriod::create($mStart, '1 month', $mEnd);
        foreach ($periodMeses as $m) {
            $meses[] = $m->format('Y-m');
        }

        // --- 3) Días + Meses (meses al final)
        $rangos = array_merge($dias, $meses);

        // --- 4) Iteración principal
        foreach ($rangos as $rangoTemporal) {
            self::procesarConsulta(null, null, $rangoTemporal, $cacheConsultas);

            foreach ($productos as $producto) {
                self::procesarConsulta($producto->producto, null, $rangoTemporal, $cacheConsultas);
            }
            foreach ($clientes as $cliente) {
                self::procesarConsulta(null, $cliente->cliente, $rangoTemporal, $cacheConsultas);
            }
            foreach ($productos as $producto) {
                foreach ($clientes as $cliente) {
                    self::procesarConsulta($producto->producto, $cliente->cliente, $rangoTemporal, $cacheConsultas);
                }
            }
        }
    }

}
