Samuel Steven Bernal Martínez
Diego Andrés García Mendoza
Jesús García Rojo
Leyla Zumaran
Cristian Contreras
Edumedia Creando Juntos
Gonzalo Tomas Orlando
Jhon Fredy Mendieta Suárez
Bryan Castano
Francisco Manuel Sanchez Morales
No se mucho pero creo que hay un error en el proyecto proporcionado, yo lo descargué y lo puse a andar tal cual, pero en la consola noté unas peticiones que no paraban de hacerse, nada más iniciar el proyecto sin tocar nada:
Parece que es algo relacionado con La URL donde da error 404 y reintenta infinitamente, lo que hice fue meterlo en un try catch para solucionarlo temporalmente, pero seria bueno que lo revisen, o no se si solo soy yo :)
Me pasa lo mismo 😢
Quizá se están borrando los archivos por estudiantes como nosotros, cada vez me aparecen menos proudctos
La solución al fallo es poner :
onerror="this.onerror=null; delante de this.src.
Me funcionó!
Eso lo ponen en el archivo index.js linea 70
Devs, encontré un ejemplo bastante práctico y fácil de entender para implementar async/await en solicitudes HTTP.
Solucion a problemas de consola.
Lo que hacemos es cambiar la url que esta hardcodeada y caida por un lado, y por otro cambiar cuando viene la respuesta de la API con la misma URL que esta caida.
En repositories.js poner
class ProductRepository { constructor(url) { this.url = url; } async getProducts(categoryId) { const filterParams = new URLSearchParams(); if (categoryId) { filterParams.append("categoryId", categoryId); } let url = `${this.url}/products/`; if (filterParams.size > 0) { url = `${url}?${filterParams}`; } const response = await fetch(url); if ( !response.ok && !response.headers.get("content-type") != "applicacion/json" ) { throw new Error("No fue posible encontrar los productos"); } const responseData = await response.json(); return responseData.map((p) => ({ ...p, images: p.images.map((img) => img.includes("placeimg") ? "https://picsum.photos/640/480" : img, ), })); } async getProduct(id) { const response = await fetch(`${this.url}/products/${id}/`); if ( !response.ok && !response.headers.get("content-type") != "applicacion/json" ) { throw new Error("No fue posible encontrar los productos"); } const responseData = await response.json(); return responseData.map((p) => ({ ...p, images: p.images.map((img) => img.includes("placeimg") ? "https://picsum.photos/640/480" : img, ), })); } async createProduct(product) { const url = `${this.url}/products/`; const response = await fetch(url, { method: "POST", headers: { Accept: "application/json", "Content-Type": "application/json", }, body: JSON.stringify(product), }); if (!response.ok) { throw new Error( `No se pudo crear el producto, con status code ${response.status}`, ); } const jsonResponse = await response.json(); return jsonResponse; } async editProduct(productId, productData) { const url = `${this.url}/products/${productId}/`; const response = await fetch(url, { method: "PUT", headers: { Accept: "application/json", "Content-Type": "application/json", }, body: JSON.stringify(productData), }); if (!response.ok) { throw new Error( `No se puede actualizar el producto ${productId}: ${response.status}`, ); } const jsonResponse = await response.json(); return jsonResponse; } async deleteProduct(productId) { const url = `${this.url}/products/${productId}/`; const response = await fetch(url, { method: "DELETE", headers: { Accept: "application/json", "Content-Type": "application/json", }, }); return response.ok; } } window.productRepository = new ProductRepository( "https://api.escuelajs.co/api/v1", );
Y en index.js
/** * Index JavaScript - Lógica específica del catálogo de productos * Maneja: listado, búsqueda, filtrado y eliminación de productos */ // ===== ESTADO DE LA APLICACIÓN ===== let currentProducts = [...MOCK_PRODUCTS]; let currentCategory = ""; let currentSearch = ""; let productToDelete = null; // ===== ELEMENTOS DEL DOM ===== let productsGrid; let emptyState; let searchInput; let categoryFilter; let deleteModal; // ===== INICIALIZACIÓN ===== document.addEventListener("DOMContentLoaded", async () => { await initializeCatalog(); }); /** * Inicializa el catálogo de productos */ async function initializeCatalog() { // Obtener elementos del DOM productsGrid = document.getElementById("productsGrid"); emptyState = document.getElementById("emptyState"); searchInput = document.getElementById("searchInput"); categoryFilter = document.getElementById("categoryFilter"); deleteModal = document.getElementById("deleteModal"); // Inicializar componentes populateCategories(categoryFilter); bindEventListeners(); const repository = window.productRepository; const productResponse = await repository.getProducts(); console.log(productResponse); // Simular carga inicial simulateLoading(() => { renderProducts(productResponse); }); } // ===== RENDERIZADO DE PRODUCTOS ===== /** * Renderiza la lista de productos en el catálogo * @param {Array} products - Array de productos a mostrar */ function renderProducts(products) { hideAllStates(); if (products.length === 0) { emptyState.style.display = "block"; return; } productsGrid.innerHTML = products .map( (product) => ` <article class="product-card"> <a href="detail.html?id=${product.id}" class="product-card-link"> <img src="${product.images[0]}" alt="${product.title}" class="product-image" onerror="this.src='https://placehold.co/280x200?text=No+Image'" > </a> <div class="product-content"> <div class="product-category">${product.category.name}</div> <h3 class="product-title"> <a href="detail.html?id=${product.id}" class="product-title-link">${product.title}</a> </h3> <p class="product-description">${product.description}</p> <div class="product-footer"> <span class="product-price">$${product.price}</span> <div class="product-actions"> <a href="detail.html?id=${product.id}" class="btn btn-primary btn-sm"> 👁️ Ver </a> <a href="edit.html?id=${product.id}" class="btn btn-secondary btn-sm"> ✏️ Editar </a> <button onclick="showDeleteModal(${product.id})" class="btn btn-danger btn-sm" > 🗑️ Eliminar </button> </div> </div> </div> </article> `, ) .join(""); } // ===== FILTRADO Y BÚSQUEDA ===== /** * Filtra productos según categoría y búsqueda actual */ async function filterProducts() { let filtered = [...MOCK_PRODUCTS]; // Filtrar por categoría if (currentCategory) { filtered = filtered.filter( (product) => product.category.id.toString() === currentCategory, ); } // Filtrar por búsqueda if (currentSearch) { filtered = filtered.filter( (product) => product.title.toLowerCase().includes(currentSearch.toLowerCase()) || product.description.toLowerCase().includes(currentSearch.toLowerCase()), ); } currentProducts = filtered; renderProducts(filtered); } // ===== EVENTOS ===== /** * Configura todos los event listeners del catálogo */ function bindEventListeners() { // Búsqueda en tiempo real searchInput.addEventListener("input", (e) => { currentSearch = e.target.value; filterProducts(); }); // Filtro por categoría categoryFilter.addEventListener("change", (e) => { currentCategory = e.target.value; filterProducts(); }); // Modal de eliminación if (deleteModal) { document .getElementById("confirmDelete") .addEventListener("click", deleteProduct); document .getElementById("cancelDelete") .addEventListener("click", hideDeleteModal); } } // ===== ELIMINACIÓN DE PRODUCTOS ===== /** * Muestra el modal de confirmación para eliminar producto * @param {number} productId - ID del producto a eliminar */ function showDeleteModal(productId) { productToDelete = productId; const product = findProductById(productId); // Obtener el modal cada vez por si no estaba inicializado const modal = deleteModal || document.getElementById("deleteModal"); if (product && modal) { // Actualizar texto del modal con nombre del producto const modalText = document.querySelector("#deleteModal p"); if (modalText) { modalText.textContent = `¿Estás seguro de que quieres eliminar "${product.title}"?`; } modal.style.display = "flex"; } } /** * Oculta el modal de confirmación */ function hideDeleteModal() { productToDelete = null; const modal = deleteModal || document.getElementById("deleteModal"); if (modal) { modal.style.display = "none"; } } /** * Elimina el producto seleccionado */ function deleteProduct() { if (!productToDelete) return; const confirmBtn = document.getElementById("confirmDelete"); // Mostrar estado de carga en el botón if (confirmBtn) { confirmBtn.innerHTML = '<div class="spinner"></div> Eliminando...'; } // Simular operación de eliminación setTimeout(() => { // Eliminar del array mock const wasDeleted = removeProductFromMock(productToDelete); hideDeleteModal(); if (wasDeleted) { showMessage(MOCK_MESSAGES.delete.success, "success"); // Re-renderizar la lista filtrada filterProducts(); } else { showMessage(MOCK_MESSAGES.delete.error, "error"); } // Restaurar botón if (confirmBtn) { confirmBtn.innerHTML = "Eliminar"; } }, 1000); } // ===== FUNCIONES AUXILIARES ===== /** * Actualiza el catálogo después de cambios externos * Útil cuando se regresa desde crear/editar producto */ function refreshCatalog() { currentProducts = [...MOCK_PRODUCTS]; filterProducts(); } // Exponer función global para que otras páginas puedan actualizar el catálogo window.refreshCatalog = refreshCatalog; // Exponer función global para el onclick del botón eliminar window.showDeleteModal = showDeleteModal;
No he podido con la API, tiene un error, alguien a podido solucionar?
Something to Share.
const prods = fetch = await fetch('https://api.escuelajs.co/api/v1/products/').then( err => err.json()).then( l1st => { console.log('productos: ', l1st )}); VM2801:1 productos: (147) [{…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, …] [0 … 99] 0 : {id: 23, title: 'Sleek Modern Laptop with Ambient Lighting', slug: 'sleek-modern-laptop-with-ambient-lighting', price: 43, description: 'Experience next-level computing with our ultra-sli…ection, making it as stylish as it is functional.', …} 1 : {id: 24, title: 'Sleek Modern Laptop for Professionals', slug: 'sleek-modern-laptop-for-professionals', price: 97, description: 'Experience cutting-edge technology and elegant des…tability. Shop now to elevate your work and play.', …} 2 : {id: 26, title: 'Sleek Mirror Finish Phone Case', slug: 'sleek-mirror-finish-phone-case', price: 27, description: "Enhance your smartphone's look with this ultra-sle…those who love a minimalist and modern aesthetic.", …} 3 : {id: 27, title: 'Sleek Smartwatch with Vibrant Display', slug: 'sleek-smartwatch-with-vibrant-display', price: 16, description: 'Experience modern timekeeping with our high-tech s…ile showcasing exceptional style and versatility.', …} 4 : {id: 28, title: 'Sleek Modern Leather Sofa Couch', slug: 'sleek-modern-leather-sofa-couch', price: 56, description: 'Enhance the elegance of your living space with our…ment piece that combines style with practicality.', …} 5 : {id: 29, title: 'Mid-Century Modern Wooden Dining Table', slug: 'mid-century-modern-wooden-dining-table', price: 24, description: 'Elevate your dining room with this sleek Mid-Centu…ers or as a stylish spot for your morning coffee.', …} 6 : {id: 30, title: 'Elegant Golden-Base Stone Top Dining Table', slug: 'elegant-golden-base-stone-top-dining-table', price: 66, description: 'Elevate your dining space with this luxurious tabl…able easily complements any modern or glam decor.', …} 7 : {id: 31, title: 'Modern Elegance Teal Armchair', slug: 'modern-elegance-teal-armchair', price: 25, description: 'Elevate your living space with this beautifully cr…, relaxing, or creating a cozy conversation nook.', …} 8 : {id: 32, title: 'Elegant Solid Wood Dining Table', slug: 'elegant-solid-wood-dining-table', price: 67, description: 'Enhance your dining space with this sleek, contemp…nterpiece. The overhead lighting is not included.', …} 9 : {id: 33, title: 'Modern Minimalist Workstation Setup', slug: 'modern-minimalist-workstation-setup', price: 49, description: 'Elevate your home office with our Modern Minimalis…ary look that combines functionality with design.', …} 10 : {id: 34, title: 'Modern Ergonomic Office Chair', slug: 'modern-ergonomic-office-chair', price: 71, description: 'Elevate your office space with this sleek and comf…versatile addition to any contemporary workspace.', …} 11 : {id: 35, title: 'Futuristic Holographic Soccer Cleats', slug: 'futuristic-holographic-soccer-cleats', price: 39, description: 'Step onto the field and stand out from the crowd w…on-forward athlete who wants to make a statement.', …} 12 : {id: 36, title: 'Rainbow Glitter High Heels', slug: 'rainbow-glitter-high-heels', price: 39, description: 'Step into the spotlight with these eye-catching ra… are sure to turn heads and elevate any ensemble.', …} 13 : {id: 37, title: 'Chi
Hay un bucle infinito en el proyecto por que no encuentra el recurso por ahora borre esa linea, otra solución seria apuntar a un recurso que si pueda encontrar