diff --git a/eltiempo/js/estadisticas.js b/eltiempo/js/estadisticas.js index 80d9616..13a0e71 100644 --- a/eltiempo/js/estadisticas.js +++ b/eltiempo/js/estadisticas.js @@ -1,7 +1,3 @@ -// API URL absoluta -const API_URL = "https://aplicacionesdevanguardia.es/eltiempo/servidor/api-weather.php?ciudad=madrid"; -const API_URL_TODAY = "https://aplicacionesdevanguardia.es/eltiempo/servidor/weather-hoy.php?ciudad=madrid"; - // ==================== // Helper // ==================== @@ -11,44 +7,50 @@ const monthNames = ["Enero","Febrero","Marzo","Abril","Mayo","Junio", "Julio","Agosto","Septiembre","Octubre","Noviembre","Diciembre"]; let selectedMonth = new Date().getMonth(); // mes actual +let ciudadActual = "Madrid"; // ciudad por defecto +const BASE_API = "https://aplicacionesdevanguardia.es/eltiempo/servidor/api-weather-fechas.php"; + +// ==================== +// Construir URL de API según filtros +// ==================== +function buildApiUrl({ ciudad, fecha = null, desde = null, hasta = null }) { + const params = new URLSearchParams(); + params.append("ciudad", ciudad); + if (fecha) params.append("fecha", fecha); + if (desde) params.append("desde", desde); + if (hasta) params.append("hasta", hasta); + + return `${BASE_API}?${params.toString()}`; +} + +// ==================== +// Actualizar nombre de mes +// ==================== function updateMonthHeader() { $("mes-nombre").textContent = monthNames[selectedMonth]; } // ==================== -// Cargar datos del JSON +// Cargar datos desde la API // ==================== -async function loadStats() { +async function loadStats(options = {}) { try { - const response = await fetch(API_URL); + const url = buildApiUrl({ + ciudad: ciudadActual, + ...options + }); + + const response = await fetch(url); if (!response.ok) throw new Error("Error cargando datos: " + response.status); const data = await response.json(); - if (!data || !data.length) throw new Error("Datos vacíos"); renderLastData(data); renderMonthStats(data); renderTrend(data); - } catch (err) { - console.error(err); - $("stats-location").textContent = "Error cargando datos"; - } -} -async function loadStatsToday() { - try { - const response = await fetch(API_URL); - if (!response.ok) throw new Error("Error cargando datos: " + response.status); - - const data = await response.json(); - - if (!data || !data.length) throw new Error("Datos vacíos"); - - renderLastData(data); - renderMonthStats(data); - renderTrend(data); } catch (err) { console.error(err); $("stats-location").textContent = "Error cargando datos"; @@ -67,11 +69,13 @@ function renderLastData(data) { $("last-wind").textContent = last.viento_velocidad + " km/h"; $("last-sunrise").textContent = last.amanecer; $("last-sunset").textContent = last.anochecer; -} -/* ============================================== - FASES LUNARES -============================================== */ + $("stats-location").textContent = `${ciudadActual} - Estadísticas`; +} + +// ==================== +// FASES LUNARES +// ==================== function getMoonPhase() { const now = new Date(); const year = now.getFullYear(); @@ -96,32 +100,13 @@ function getMoonPhase() { else if (age < 27.68493) { phaseName = "Creciente Menguante"; icon = "🌘"; } else { phaseName = "Luna Nueva"; icon = "🌑"; } - document.getElementById("moon-phase").textContent = phaseName; - document.getElementById("moon-icon").textContent = icon; - - document.getElementById("stats-location").textContent = `Hoy es ${day} de ${monthNames[month-1]} de ${year}`; + $("moon-phase").textContent = phaseName; + $("moon-icon").textContent = icon; + const today = new Date(); + $("stats-location").textContent += ` | Hoy ${today.getDate()} de ${monthNames[today.getMonth()]} de ${today.getFullYear()}`; } -function moonPhaseForDate(year, month, day) { - const c = Math.floor(365.25 * year); - const e = Math.floor(30.6 * (month + 1)); - const jd = c + e + day - 694039.09; - const phase = (jd / 29.53) % 1; - const age = phase * 29.53; - - if (age < 1.84566) return "🌑"; - if (age < 5.53699) return "🌒"; - if (age < 9.22831) return "🌓"; - if (age < 12.91963) return "🌔"; - if (age < 16.61096) return "🌕"; - if (age < 20.30228) return "🌖"; - if (age < 23.99361) return "🌗"; - if (age < 27.68493) return "🌘"; - return "🌑"; -} - - // ==================== // Estadísticas del mes seleccionado // ==================== @@ -164,16 +149,13 @@ function renderTrend(data) { const container = $("trend-container"); - // Si no hay datos para el mes, mostrar mensaje if (Object.keys(byYear).length === 0) { container.innerHTML = "

No hay datos históricos para este mes.

"; return; } - // Construir tabla completa en una variable let html = ""; - // Ordenar años de más reciente a más antiguo Object.keys(byYear).sort((a, b) => b - a).forEach(year => { const maxAvg = (byYear[year].max.reduce((a,b)=>a+b,0)/byYear[year].max.length).toFixed(1); const minAvg = (byYear[year].min.reduce((a,b)=>a+b,0)/byYear[year].min.length).toFixed(1); @@ -190,33 +172,49 @@ function renderTrend(data) { }); html += "
AñoMáxMínLluvia
"; - - // Insertar tabla en el contenedor container.innerHTML = html; } +// ==================== +// Cargar mes actual según selectedMonth +// ==================== +function loadCurrentMonth() { + const year = new Date().getFullYear(); + const month = selectedMonth + 1; + + const firstDay = `${year}-${String(month).padStart(2,"0")}-01`; + const lastDay = new Date(year, month, 0).toISOString().split("T")[0]; + + loadStats({ desde: firstDay, hasta: lastDay }); +} // ==================== -// Botones de mes +// Inicialización // ==================== document.addEventListener("DOMContentLoaded", () => { updateMonthHeader(); - loadStats(); - loadStatsToday(); + getMoonPhase(); + loadCurrentMonth(); $("prev-month").addEventListener("click", () => { selectedMonth = (selectedMonth + 11) % 12; updateMonthHeader(); - loadStats(); - loadStatsToday(); + loadCurrentMonth(); }); $("next-month").addEventListener("click", () => { selectedMonth = (selectedMonth + 1) % 12; updateMonthHeader(); - loadStats(); - loadStatsToday(); + loadCurrentMonth(); }); $("year").textContent = new Date().getFullYear(); + + // ==================== + // Selector de ciudad + // ==================== + $("city-select").addEventListener("change", (e) => { + ciudadActual = e.target.value; + loadCurrentMonth(); + }); }); diff --git a/eltiempo/servidor/api-weather-fechas.php b/eltiempo/servidor/api-weather-fechas.php new file mode 100644 index 0000000..1e60bd1 --- /dev/null +++ b/eltiempo/servidor/api-weather-fechas.php @@ -0,0 +1,150 @@ + "El parámetro 'ciudad' es obligatorio."]); + exit(); +} + +// Validar formato fecha YYYY-MM-DD +function fechaValida($fecha) { + return preg_match('/^\d{4}-\d{2}-\d{2}$/', $fecha); +} + +if (!empty($fecha) && !fechaValida($fecha)) { + http_response_code(400); + echo json_encode(["error" => "El parámetro 'fecha' debe tener formato YYYY-MM-DD."]); + exit(); +} + +if (!empty($desde) && !fechaValida($desde)) { + http_response_code(400); + echo json_encode(["error" => "El parámetro 'desde' debe tener formato YYYY-MM-DD."]); + exit(); +} + +if (!empty($hasta) && !fechaValida($hasta)) { + http_response_code(400); + echo json_encode(["error" => "El parámetro 'hasta' debe tener formato YYYY-MM-DD."]); + exit(); +} + +// ============================ +// 4. CONEXIÓN BD +// ============================ +$conn = new mysqli(DB_HOST, DB_USER, DB_PASS, DB_NAME); + +if ($conn->connect_error) { + http_response_code(500); + echo json_encode(["error" => "Error de conexión a la base de datos."]); + exit(); +} + +// ============================ +// 5. CONSTRUIR QUERY DINÁMICA +// ============================ +$sql = " + SELECT + DATE(fecha) AS dia, + MIN(fecha) AS primera_fecha_del_dia, + MIN(amanecer) AS amanecer, + MAX(anochecer) AS anochecer, + MAX(temp_max) AS temp_max, + MIN(temp_min) AS temp_min, + AVG(humedad) AS humedad, + SUM(lluvia) AS lluvia, + AVG(nubes) AS nubes, + AVG(viento_velocidad) AS viento_velocidad, + AVG(viento_direccion) AS viento_direccion + FROM weather + WHERE ciudad LIKE CONCAT('%', ?, '%') +"; + +$params = [$ciudad]; +$types = "s"; + +// Filtro por fecha exacta +if (!empty($fecha)) { + $sql .= " AND DATE(fecha) = ?"; + $params[] = $fecha; + $types .= "s"; +} + +// Filtro por rango +if (!empty($desde) && !empty($hasta)) { + $sql .= " AND DATE(fecha) BETWEEN ? AND ?"; + $params[] = $desde; + $params[] = $hasta; + $types .= "ss"; +} elseif (!empty($desde)) { + $sql .= " AND DATE(fecha) >= ?"; + $params[] = $desde; + $types .= "s"; +} elseif (!empty($hasta)) { + $sql .= " AND DATE(fecha) <= ?"; + $params[] = $hasta; + $types .= "s"; +} + +$sql .= " + GROUP BY DATE(fecha) + ORDER BY DATE(fecha); +"; + +// ============================ +// 6. PREPARAR Y EJECUTAR +// ============================ +$stmt = $conn->prepare($sql); + +if (!$stmt) { + http_response_code(500); + echo json_encode(["error" => "Error al preparar la consulta."]); + $conn->close(); + exit(); +} + +$stmt->bind_param($types, ...$params); +$stmt->execute(); +$result = $stmt->get_result(); + +$datos = []; + +while ($row = $result->fetch_assoc()) { + $datos[] = $row; +} + +// ============================ +// 7. RESPUESTA +// ============================ +http_response_code(200); +echo json_encode($datos); + +// ============================ +// 8. CIERRE +// ============================ +$stmt->close(); +$conn->close(); +?> diff --git a/eltiempo/servidor/api-weather.php b/eltiempo/servidor/api-weather.php index 37a811f..dddb587 100755 --- a/eltiempo/servidor/api-weather.php +++ b/eltiempo/servidor/api-weather.php @@ -14,6 +14,7 @@ define('DB_NAME', 'clima'); // --- 2. Input Validation --- // Assuming 'ciudad' comes from a GET request. $ciudad = $_GET['ciudad'] ?? ''; // Use null coalescing operator for cleaner default +$fecha = $_GET['fecha'] ?? ''; // Optional: filter by date if (empty($ciudad)) { http_response_code(400); // Bad Request @@ -21,6 +22,12 @@ if (empty($ciudad)) { exit(); // Stop script execution } +if (!empty($fecha) && !preg_match('/^\d{4}-\d{2}-\d{2}$/', $fecha)) { + http_response_code(400); // Bad Request + echo json_encode(["error" => "Parámetro 'fecha' debe tener el formato YYYY-MM-DD."]); + exit(); +} + // Optional: Further sanitize/validate the city name if needed (e.g., alphanumeric only) // if (!preg_match('/^[a-zA-Z\s]+$/', $ciudad)) { // http_response_code(400); @@ -53,6 +60,7 @@ $stmt = $conn->prepare(" FROM weather WHERE DATE(fecha) >= '2024-10-01' AND ciudad LIKE CONCAT('%', ?, '%') + AND DATE(fecha) LIKE CONCAT('%', ?, '%') GROUP BY DATE(fecha) ORDER BY DATE(fecha); "); @@ -64,7 +72,8 @@ if (!$stmt) { exit(); } -$stmt->bind_param("s", $ciudad); +$stmt->bind_param("ss", $ciudad, $fecha); + $stmt->execute(); $result = $stmt->get_result();