mejorando apis para utilizar fechas
This commit is contained in:
parent
ff4835cc66
commit
3f3e098177
|
|
@ -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
|
// Helper
|
||||||
// ====================
|
// ====================
|
||||||
|
|
@ -11,44 +7,50 @@ const monthNames = ["Enero","Febrero","Marzo","Abril","Mayo","Junio",
|
||||||
"Julio","Agosto","Septiembre","Octubre","Noviembre","Diciembre"];
|
"Julio","Agosto","Septiembre","Octubre","Noviembre","Diciembre"];
|
||||||
|
|
||||||
let selectedMonth = new Date().getMonth(); // mes actual
|
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() {
|
function updateMonthHeader() {
|
||||||
$("mes-nombre").textContent = monthNames[selectedMonth];
|
$("mes-nombre").textContent = monthNames[selectedMonth];
|
||||||
}
|
}
|
||||||
|
|
||||||
// ====================
|
// ====================
|
||||||
// Cargar datos del JSON
|
// Cargar datos desde la API
|
||||||
// ====================
|
// ====================
|
||||||
async function loadStats() {
|
async function loadStats(options = {}) {
|
||||||
try {
|
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);
|
if (!response.ok) throw new Error("Error cargando datos: " + response.status);
|
||||||
|
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
||||||
if (!data || !data.length) throw new Error("Datos vacíos");
|
if (!data || !data.length) throw new Error("Datos vacíos");
|
||||||
|
|
||||||
renderLastData(data);
|
renderLastData(data);
|
||||||
renderMonthStats(data);
|
renderMonthStats(data);
|
||||||
renderTrend(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) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
$("stats-location").textContent = "Error cargando datos";
|
$("stats-location").textContent = "Error cargando datos";
|
||||||
|
|
@ -67,11 +69,13 @@ function renderLastData(data) {
|
||||||
$("last-wind").textContent = last.viento_velocidad + " km/h";
|
$("last-wind").textContent = last.viento_velocidad + " km/h";
|
||||||
$("last-sunrise").textContent = last.amanecer;
|
$("last-sunrise").textContent = last.amanecer;
|
||||||
$("last-sunset").textContent = last.anochecer;
|
$("last-sunset").textContent = last.anochecer;
|
||||||
|
|
||||||
|
$("stats-location").textContent = `${ciudadActual} - Estadísticas`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ==============================================
|
// ====================
|
||||||
FASES LUNARES
|
// FASES LUNARES
|
||||||
============================================== */
|
// ====================
|
||||||
function getMoonPhase() {
|
function getMoonPhase() {
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const year = now.getFullYear();
|
const year = now.getFullYear();
|
||||||
|
|
@ -96,32 +100,13 @@ function getMoonPhase() {
|
||||||
else if (age < 27.68493) { phaseName = "Creciente Menguante"; icon = "🌘"; }
|
else if (age < 27.68493) { phaseName = "Creciente Menguante"; icon = "🌘"; }
|
||||||
else { phaseName = "Luna Nueva"; icon = "🌑"; }
|
else { phaseName = "Luna Nueva"; icon = "🌑"; }
|
||||||
|
|
||||||
document.getElementById("moon-phase").textContent = phaseName;
|
$("moon-phase").textContent = phaseName;
|
||||||
document.getElementById("moon-icon").textContent = icon;
|
$("moon-icon").textContent = icon;
|
||||||
|
|
||||||
document.getElementById("stats-location").textContent = `Hoy es ${day} de ${monthNames[month-1]} de ${year}`;
|
|
||||||
|
|
||||||
|
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
|
// Estadísticas del mes seleccionado
|
||||||
// ====================
|
// ====================
|
||||||
|
|
@ -164,16 +149,13 @@ function renderTrend(data) {
|
||||||
|
|
||||||
const container = $("trend-container");
|
const container = $("trend-container");
|
||||||
|
|
||||||
// Si no hay datos para el mes, mostrar mensaje
|
|
||||||
if (Object.keys(byYear).length === 0) {
|
if (Object.keys(byYear).length === 0) {
|
||||||
container.innerHTML = "<p>No hay datos históricos para este mes.</p>";
|
container.innerHTML = "<p>No hay datos históricos para este mes.</p>";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Construir tabla completa en una variable
|
|
||||||
let html = "<table class=\"trend-table\"><thead><tr><th>Año</th><th>Máx</th><th>Mín</th><th>Lluvia</th></tr></thead><tbody>";
|
let html = "<table class=\"trend-table\"><thead><tr><th>Año</th><th>Máx</th><th>Mín</th><th>Lluvia</th></tr></thead><tbody>";
|
||||||
|
|
||||||
// Ordenar años de más reciente a más antiguo
|
|
||||||
Object.keys(byYear).sort((a, b) => b - a).forEach(year => {
|
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 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);
|
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 += "</tbody></table>";
|
html += "</tbody></table>";
|
||||||
|
|
||||||
// Insertar tabla en el contenedor
|
|
||||||
container.innerHTML = html;
|
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", () => {
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
updateMonthHeader();
|
updateMonthHeader();
|
||||||
loadStats();
|
getMoonPhase();
|
||||||
loadStatsToday();
|
loadCurrentMonth();
|
||||||
|
|
||||||
$("prev-month").addEventListener("click", () => {
|
$("prev-month").addEventListener("click", () => {
|
||||||
selectedMonth = (selectedMonth + 11) % 12;
|
selectedMonth = (selectedMonth + 11) % 12;
|
||||||
updateMonthHeader();
|
updateMonthHeader();
|
||||||
loadStats();
|
loadCurrentMonth();
|
||||||
loadStatsToday();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
$("next-month").addEventListener("click", () => {
|
$("next-month").addEventListener("click", () => {
|
||||||
selectedMonth = (selectedMonth + 1) % 12;
|
selectedMonth = (selectedMonth + 1) % 12;
|
||||||
updateMonthHeader();
|
updateMonthHeader();
|
||||||
loadStats();
|
loadCurrentMonth();
|
||||||
loadStatsToday();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
$("year").textContent = new Date().getFullYear();
|
$("year").textContent = new Date().getFullYear();
|
||||||
|
|
||||||
|
// ====================
|
||||||
|
// Selector de ciudad
|
||||||
|
// ====================
|
||||||
|
$("city-select").addEventListener("change", (e) => {
|
||||||
|
ciudadActual = e.target.value;
|
||||||
|
loadCurrentMonth();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,150 @@
|
||||||
|
<?php
|
||||||
|
// ============================
|
||||||
|
// 1. HEADERS
|
||||||
|
// ============================
|
||||||
|
header("Access-Control-Allow-Origin: *");
|
||||||
|
header("Content-Type: application/json; charset=utf-8");
|
||||||
|
|
||||||
|
// ============================
|
||||||
|
// 2. CONFIGURACIÓN BD
|
||||||
|
// (En producción esto debería ir
|
||||||
|
// en un archivo fuera del webroot)
|
||||||
|
// ============================
|
||||||
|
define('DB_HOST', 'localhost');
|
||||||
|
define('DB_USER', 'admin');
|
||||||
|
define('DB_PASS', 'Eavne,e1m');
|
||||||
|
define('DB_NAME', 'clima');
|
||||||
|
|
||||||
|
// ============================
|
||||||
|
// 3. VALIDACIÓN DE ENTRADA
|
||||||
|
// ============================
|
||||||
|
$ciudad = $_GET['ciudad'] ?? '';
|
||||||
|
$fecha = $_GET['fecha'] ?? '';
|
||||||
|
$desde = $_GET['desde'] ?? '';
|
||||||
|
$hasta = $_GET['hasta'] ?? '';
|
||||||
|
|
||||||
|
if (empty($ciudad)) {
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode(["error" => "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();
|
||||||
|
?>
|
||||||
|
|
@ -14,6 +14,7 @@ define('DB_NAME', 'clima');
|
||||||
// --- 2. Input Validation ---
|
// --- 2. Input Validation ---
|
||||||
// Assuming 'ciudad' comes from a GET request.
|
// Assuming 'ciudad' comes from a GET request.
|
||||||
$ciudad = $_GET['ciudad'] ?? ''; // Use null coalescing operator for cleaner default
|
$ciudad = $_GET['ciudad'] ?? ''; // Use null coalescing operator for cleaner default
|
||||||
|
$fecha = $_GET['fecha'] ?? ''; // Optional: filter by date
|
||||||
|
|
||||||
if (empty($ciudad)) {
|
if (empty($ciudad)) {
|
||||||
http_response_code(400); // Bad Request
|
http_response_code(400); // Bad Request
|
||||||
|
|
@ -21,6 +22,12 @@ if (empty($ciudad)) {
|
||||||
exit(); // Stop script execution
|
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)
|
// Optional: Further sanitize/validate the city name if needed (e.g., alphanumeric only)
|
||||||
// if (!preg_match('/^[a-zA-Z\s]+$/', $ciudad)) {
|
// if (!preg_match('/^[a-zA-Z\s]+$/', $ciudad)) {
|
||||||
// http_response_code(400);
|
// http_response_code(400);
|
||||||
|
|
@ -53,6 +60,7 @@ $stmt = $conn->prepare("
|
||||||
FROM weather
|
FROM weather
|
||||||
WHERE DATE(fecha) >= '2024-10-01'
|
WHERE DATE(fecha) >= '2024-10-01'
|
||||||
AND ciudad LIKE CONCAT('%', ?, '%')
|
AND ciudad LIKE CONCAT('%', ?, '%')
|
||||||
|
AND DATE(fecha) LIKE CONCAT('%', ?, '%')
|
||||||
GROUP BY DATE(fecha)
|
GROUP BY DATE(fecha)
|
||||||
ORDER BY DATE(fecha);
|
ORDER BY DATE(fecha);
|
||||||
");
|
");
|
||||||
|
|
@ -64,7 +72,8 @@ if (!$stmt) {
|
||||||
exit();
|
exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
$stmt->bind_param("s", $ciudad);
|
$stmt->bind_param("ss", $ciudad, $fecha);
|
||||||
|
|
||||||
$stmt->execute();
|
$stmt->execute();
|
||||||
$result = $stmt->get_result();
|
$result = $stmt->get_result();
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue