Aplicaciones Web Orientadas a Servicios • ITIID-05 •
Práctica Guiada: Mine-API - 2
script.js
// CONFIGURACIÓN
const YOUTUBE_API_KEY = "";
const BASE_API_URL = "https://mine-api-jade.vercel.app/api";
// Estado global de la vista actual
let currentData = [];
let currentCategory = 'personajes';
// Inicializa la app con la categoría por defecto
async function init() {
await fetchData('personajes');
}
// Cambia de categoría, actualiza UI y recarga datos
async function changeCategory(category) {
currentCategory = category;
document.querySelectorAll('.mc-nav-btn').forEach(btn => btn.classList.remove('active'));
const activeBtn = document.getElementById(`btn-${category.replace('/', '-')}`);
if (activeBtn) activeBtn.classList.add('active');
await fetchData(category);
closeSidebar();
}
// Consulta datos en la API y normaliza la respuesta
async function fetchData(endpoint) {
const loading = document.getElementById('loading');
const grid = document.getElementById('contentGrid');
loading.classList.remove('hidden');
grid.innerHTML = '';
try {
const response = await fetch(`${BASE_API_URL}/${endpoint}`);
const data = await response.json();
if (Array.isArray(data)) {
currentData = data;
} else if (data && typeof data === 'object') {
const possibleArray = Object.values(data).find(val => Array.isArray(val));
currentData = possibleArray || [];
} else {
currentData = [];
}
loading.classList.add('hidden');
renderContent(currentData);
} catch (error) {
loading.innerText = "Error: No se pudo conectar con el servidor.";
console.error("Error fetching data:", error);
}
}
// Dibuja las tarjetas del listado principal
function renderContent(data) {
const grid = document.getElementById('contentGrid');
grid.innerHTML = '';
if (!Array.isArray(data) || data.length === 0) {
grid.innerHTML = '<p class="col-span-full text-center text-xl text-red-400">No se encontraron resultados o la API devolvió un formato inesperado.</p>';
return;
}
data.forEach(item => {
if (!item) return;
const card = document.createElement('div');
card.className = 'mc-card p-4 flex flex-col items-center cursor-pointer text-center';
card.onclick = () => showDetails(item);
const name = item.nombre || item.name || "Desconocido";
const imgUrl = item.imagen || item.image || 'https://via.placeholder.com/150?text=Minecraft';
card.innerHTML = `
<img src="${imgUrl}" alt="${name}" class="w-24 h-24 mb-3 object-contain">
<span class="text-2xl leading-none uppercase text-white">${name}</span>
<span class="badge bg-black/40 text-gray-400 uppercase text-xs">${currentCategory.split('/')[0]}</span>
`;
grid.appendChild(card);
});
}
// Muestra el detalle del elemento seleccionado
async function showDetails(item) {
const sidebar = document.getElementById('sidebar');
sidebar.classList.remove('hidden');
const name = item.nombre || item.name || "Desconocido";
const img = item.imagen || item.image || 'https://via.placeholder.com/150?text=Minecraft';
const desc = item.descripcion || item.description || "Sin descripción disponible.";
document.getElementById('detailName').innerText = name;
document.getElementById('detailImg').src = img;
document.getElementById('detailDesc').innerText = desc;
const statsDiv = document.getElementById('detailStats');
statsDiv.innerHTML = '';
if (item.vida) statsDiv.innerHTML += `<div>❤ Vida: ${item.vida}</div>`;
if (item.comportamiento) statsDiv.innerHTML += `<div>⚙ Comportamiento: ${item.comportamiento}</div>`;
if (item.clima) statsDiv.innerHTML += `<div>☁ Clima: ${item.clima}</div>`;
if (window.innerWidth < 1024) {
sidebar.scrollIntoView({ behavior: 'smooth' });
}
fetchVideos(name);
}
// Oculta el panel lateral de detalle
function closeSidebar() {
document.getElementById('sidebar').classList.add('hidden');
}
// Busca videos relacionados en YouTube para el item activo
async function fetchVideos(queryTerm) {
const videoResults = document.getElementById('videoResults');
videoResults.innerHTML = '<p class="text-yellow-400 text-xl">Buscando guías...</p>';
if (!YOUTUBE_API_KEY) {
videoResults.innerHTML = '<p class="text-red-400 text-sm italic">Ingresa tu YouTube API Key en el código para habilitar videos.</p>';
return;
}
try {
const categoryContext = currentCategory.includes('biomas') ? 'bioma' : '';
const fullQuery = encodeURIComponent(`minecraft ${categoryContext} ${queryTerm} guide`);
const url = `https://www.googleapis.com/youtube/v3/search?part=snippet&maxResults=2&q=${fullQuery}&type=video&key=${YOUTUBE_API_KEY}`;
const response = await fetch(url);
const data = await response.json();
if (data.items && data.items.length > 0) {
videoResults.innerHTML = '';
// ✅ FIX: usar el origin sin encodeURIComponent, y manejar el caso file://
const currentOrigin = (window.location.origin && window.location.origin !== 'null')
? window.location.origin
: 'https://www.youtube.com';
data.items.forEach(video => {
const videoId = video.id.videoId;
if (videoId) {
const div = document.createElement('div');
div.className = 'video-container mb-4';
// ✅ FIX: origin sin encodeURIComponent para que YouTube lo valide correctamente
const embedUrl = `https://www.youtube.com/embed/${videoId}?enablejsapi=1&origin=${currentOrigin}`;
div.innerHTML = `<iframe
id="player-${videoId}"
type="text/html"
src="${embedUrl}"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowfullscreen></iframe>`;
videoResults.appendChild(div);
}
});
} else {
videoResults.innerHTML = '<p class="text-gray-500">No se encontraron guías.</p>';
}
} catch (error) {
console.error("YouTube API Error:", error);
videoResults.innerHTML = '<p class="text-red-500 text-sm">Error en YouTube API. Revisa tu cuota o clave.</p>';
}
}
// Filtro local por nombre dentro de la categoría actual
document.getElementById('searchInput').addEventListener('input', (e) => {
const term = e.target.value.toLowerCase();
const filtered = currentData.filter(item => {
if (!item) return false;
const name = (item.nombre || item.name || "").toLowerCase();
return name.includes(term);
});
renderContent(filtered);
});
// Punto de entrada
window.onload = init;