Para llevar a cabo la Práctica 3 – Tecnologías Web: PWA (Buscador de libros), sigue estos pasos:
Preparación del entorno:
- Instala Node.js y npm (el gestor de paquetes de Node) en tu computadora si aún no lo has hecho.
- Instala
create-react-app
usandonpm install -g create-react-app
.
Creación del proyecto React:
- Ejecuta
create-react-app buscador-libros-pwa
para crear un nuevo proyecto React con el nombre "buscador-libros-pwa". - Accede al directorio del proyecto con
cd buscador-libros-pwa
.
- Ejecuta
Instala las dependencias necesarias:
- Ejecuta
npm install axios react-router-dom
.
- Ejecuta
Estructura del proyecto:
- Crea las siguientes carpetas dentro de la carpeta
src
:components
,pages
,services
ystyles
. - Dentro de la carpeta
components
, crea componentes comoBookCard.js
,SearchBar.js
yBookDetails.js
. - En la carpeta
pages
, crea archivos comoHome.js
yBookPage.js
. - Dentro de
services
, crea un archivo llamadogoogleBooksApi.js
. - En la carpeta
styles
, crea archivos CSS para cada componente y página.
- Crea las siguientes carpetas dentro de la carpeta
Implementa la funcionalidad:
- En
googleBooksApi.js
, configura Axios para realizar consultas al API de Google Books. - En
SearchBar.js
, crea un componente de barra de búsqueda que llame al API de Google Books al buscar. - En
BookCard.js
, crea un componente para mostrar la información básica de cada libro. - En
BookDetails.js
, crea un componente para mostrar detalles adicionales del libro seleccionado. - En
Home.js
, implementa la lógica para mostrar la lista de libros y la barra de búsqueda. - En
BookPage.js
, implementa la lógica para mostrar la página de detalles de un libro específico. - Configura las rutas de navegación usando
react-router-dom
.
- En
Configura la PWA:
- En el archivo
public/manifest.json
, personaliza la información de la aplicación, como el nombre, la descripción, los iconos y los colores. - En
src/index.js
, registra el service worker:import * as serviceWorkerRegistration from './serviceWorkerRegistration';
y luegoserviceWorkerRegistration.register();
. - Crea y configura el service worker en
src/serviceWorkerRegistration.js
.
- En el archivo
Añade estilos:
- En la carpeta
styles
, crea y personaliza archivos CSS para cada componente y página.
- En la carpeta
Realiza la auditoría con Lighthouse:
- Ejecuta
npm run build
para compilar tu aplicación en la carpetabuild
. - Instala un servidor local como
serve
usandonpm install -g serve
. - Ejecuta
serve -s build
para servir la aplicación compilada localmente. - Abre la aplicación en un navegador y usa la pestaña "Lighthouse" en las herramientas de desarrollador de Chrome para realizar la auditoría.
- Ejecuta
Despliegue en GitHub Pages:
- Instala
gh-pages
como dependencia de desarrollo connpm install --save-dev gh-pages
. - En
package.json
, añade la siguiente línea en la sección "scripts":"deploy": "gh-pages -d build"
. - Ej
- Instala
Mis disculpas por eso. Continuemos con los pasos para desplegar en GitHub Pages:
Despliegue en GitHub Pages (continuación):
- Ejecuta
git init
para inicializar un repositorio Git en tu proyecto. - Crea un nuevo repositorio en GitHub y vincúlalo a tu repositorio local siguiendo las instrucciones proporcionadas en GitHub.
- Añade y realiza un commit con todos tus archivos.
- En
package.json
, añade la siguiente línea en la sección "homepage":"homepage": "https://{tu-usuario-de-github}.github.io/{nombre-de-tu-repositorio}"
, reemplazando{tu-usuario-de-github}
y{nombre-de-tu-repositorio}
con la información correspondiente. - Ejecuta
npm run deploy
para desplegar tu aplicación en GitHub Pages. - Finalmente, realiza un push a GitHub con
git push origin main
(omaster
, según la rama que estés utilizando).
- Ejecuta
Verifica el despliegue:
- Abre la URL de tu aplicación en GitHub Pages:
https://{tu-usuario-de-github}.github.io/{nombre-de-tu-repositorio}
. - Comprueba que la aplicación funcione correctamente y que cumpla con los requisitos de la práctica.
- Realiza la auditoría con Lighthouse nuevamente en la versión desplegada de tu aplicación para asegurarte de que cumpla con los criterios PWA.
Una vez que hayas completado todos estos pasos, tendrás una aplicación web progresiva (PWA) funcional que actúa como cliente del API de Google Books y está alojada en GitHub Pages.
Sí, puedes usar Bootstrap en tu proyecto React para mejorar la apariencia y la experiencia del usuario de tu aplicación. Para agregar Bootstrap a tu proyecto, sigue estos pasos:
Instala Bootstrap en tu proyecto:
- Ejecuta
npm install bootstrap
en la terminal desde el directorio de tu proyecto.
- Ejecuta
Importa Bootstrap en tu archivo
src/index.js
:- Añade la siguiente línea en la parte superior del archivo:
import 'bootstrap/dist/css/bootstrap.min.css';
- Añade la siguiente línea en la parte superior del archivo:
Utiliza los componentes y clases de estilo de Bootstrap:
- Ahora puedes usar los componentes y clases de estilo de Bootstrap en tus componentes y páginas de React. Por ejemplo, puedes utilizar la clase
container
,row
, ycol
para organizar tus elementos en una cuadrícula.
javascriptimport React from 'react'; const ExampleComponent = () => { return ( <div className="container"> <div className="row"> <div className="col"> Contenido de la columna 1 </div> <div className="col"> Contenido de la columna 2 </div> </div> </div> ); }; export default ExampleComponent;
- Ahora puedes usar los componentes y clases de estilo de Bootstrap en tus componentes y páginas de React. Por ejemplo, puedes utilizar la clase
Si deseas utilizar componentes de React específicos de Bootstrap, puedes instalar la biblioteca
reactstrap
oreact-bootstrap
:- Ejecuta
npm install reactstrap
ynpm install @popperjs/core
para instalar Reactstrap (basado en Bootstrap 5). - O ejecuta
npm install react-bootstrap bootstrap
para instalar React-Bootstrap (compatible con Bootstrap 4 y 5). - Importa y utiliza los componentes de la biblioteca elegida en tus componentes y páginas de React.
- Ejecuta
Con Bootstrap integrado en tu proyecto React, podrás aprovechar sus estilos y componentes predefinidos para diseñar una aplicación web más atractiva y fácil de usar.
A continuación, se detallan los pasos para implementar la funcionalidad requerida con explicaciones y comentarios:
- Configurar Axios en
googleBooksApi.js
:
javascriptimport axios from 'axios';
// Crea una instancia de Axios con la URL base del API de Google Books
const api = axios.create({
baseURL: 'https://www.googleapis.com/books/v1',
});
// Función para buscar libros usando el API de Google Books
export const searchBooks = async (query) => {
try {
const response = await api.get('/volumes', {
params: {
q: query,
maxResults: 10, // Limita la cantidad de resultados por búsqueda (opcional)
},
});
return response.data.items;
} catch (error) {
console.error('Error al buscar libros:', error);
return [];
}
};
- Crear componente
SearchBar.js
:
javascriptimport React, { useState } from 'react';
import { searchBooks } from './services/googleBooksApi';
const SearchBar = ({ onSearchResults }) => {
const [searchQuery, setSearchQuery] = useState('');
const handleSearch = async (event) => {
event.preventDefault();
const results = await searchBooks(searchQuery);
onSearchResults(results);
};
return (
<form onSubmit={handleSearch}>
<input
type="text"
value={searchQuery}
onChange={(event) => setSearchQuery(event.target.value)}
placeholder="Buscar libros..."
/>
<button type="submit">Buscar</button>
</form>
);
};
export default SearchBar;
- Crear componente
BookCard.js
:
javascriptimport React from 'react';
const BookCard = ({ book }) => {
const { title, authors, imageLinks } = book.volumeInfo;
const thumbnail = imageLinks?.thumbnail || 'URL_IMAGEN_ALTERNATIVA';
return (
<div>
<img src={thumbnail} alt={`Portada de ${title}`} />
<h3>{title}</h3>
<p>{authors?.join(', ') || 'Autor desconocido'}</p>
</div>
);
};
export default BookCard;
- Crear componente
BookDetails.js
:
javascriptimport React from 'react';
const BookDetails = ({ book }) => {
const { title, authors, description, imageLinks } = book.volumeInfo;
const thumbnail = imageLinks?.thumbnail || 'URL_IMAGEN_ALTERNATIVA';
return (
<div>
<img src={thumbnail} alt={`Portada de ${title}`} />
<h3>{title}</h3>
<p>{authors?.join(', ') || 'Autor desconocido'}</p>
<p>{description || 'No hay descripción disponible'}</p>
</div>
);
};
export default BookDetails;
- Implementar lógica en
Home.js
:
javascriptimport React, { useState } from 'react';
import SearchBar from './components/SearchBar';
import BookCard from './components/BookCard';
const Home = () => {
const [books, setBooks] = useState([]);
return (
<div>
<SearchBar onSearchResults={setBooks} />
<div>
{books.map((book) => (
<BookCard key={book.id} book={book} />
))}
</div>
</div>
);
};
export default Home;
- Implementar lógica en `BookPage.js
- Implementar lógica en
BookPage.js
:
javascriptimport React, { useState, useEffect } from 'react';
import { useParams } from 'react-router-dom';
import { getBookById } from './services/googleBooksApi';
import BookDetails from './components/BookDetails';
const BookPage = () => {
const { bookId } = useParams();
const [book, setBook] = useState(null);
useEffect(() => {
const fetchBook = async () => {
const bookData = await getBookById(bookId);
setBook(bookData);
};
fetchBook();
}, [bookId]);
return (
<div>
{book ? (
<BookDetails book={book} />
) : (
<p>Cargando detalles del libro...</p>
)}
</div>
);
};
export default BookPage;
- Configurar rutas de navegación usando
react-router-dom
:
Primero, instala react-router-dom
si aún no lo has hecho:
bashnpm install react-router-dom
Luego, en tu archivo src/App.js
, importa BrowserRouter
, Route
y Switch
y configura las rutas:
javascriptimport React from 'react';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import Home from './pages/Home';
import BookPage from './pages/BookPage';
const App = () => {
return (
<Router>
<Switch>
<Route exact path="/" component={Home} />
<Route path="/book/:bookId" component={BookPage} />
</Switch>
</Router>
);
};
export default App;
Estos pasos implementan la funcionalidad requerida para tu aplicación, incluyendo la búsqueda de libros a través del API de Google Books, la visualización de información básica y detalles de los libros, y la configuración de rutas de navegación con react-router-dom
. Los comentarios y las explicaciones proporcionadas te ayudarán a documentar y entender el código.
Antes de comenzar, me gustaría aclarar que en la explicación anterior, se describen componentes de React, no de React Native. Aunque React y React Native comparten similitudes y principios, son tecnologías diferentes. React es una biblioteca para construir interfaces de usuario en aplicaciones web, mientras que React Native es un marco para construir aplicaciones móviles nativas utilizando React.
Dicho esto, te explicaré el flujo de la aplicación y cómo funcionan las clases en conjunto.
App.js
: Es el componente raíz que envuelve toda la aplicación. Aquí se configuran las rutas de navegación utilizandoreact-router-dom
. Las rutas especifican qué componentes deben renderizarse según la URL en el navegador.Home.js
: Es la página principal de la aplicación que contiene la barra de búsqueda y la lista de libros. Utiliza el componenteSearchBar
yBookCard
para lograr su funcionalidad.SearchBar.js
: Este componente contiene la barra de búsqueda y es responsable de manejar las consultas de búsqueda de los usuarios. Cuando un usuario envía una búsqueda, este componente llama a la funciónsearchBooks
del archivogoogleBooksApi.js
para buscar libros usando el API de Google Books. Luego, pasa los resultados de la búsqueda al componente padre (en este caso,Home.js
) a través de la funciónonSearchResults
.googleBooksApi.js
: Este archivo contiene las funciones para interactuar con el API de Google Books utilizando Axios. La funciónsearchBooks
se encarga de realizar consultas al API y devolver los resultados.BookCard.js
: Es un componente que muestra la información básica de un libro, como la portada, el título y los autores. Se utiliza enHome.js
para renderizar la lista de libros encontrados.BookPage.js
: Este componente muestra la página de detalles de un libro específico. Utiliza la funcióngetBookById
del archivogoogleBooksApi.js
para obtener la información detallada de un libro basándose en su ID. Luego, renderiza el componenteBookDetails
con la información del libro.BookDetails.js
: Este componente muestra detalles adicionales de un libro seleccionado, como la descripción. Se utiliza enBookPage.js
para mostrar la información detallada de un libro.
El flujo general de la aplicación es el siguiente:
- El usuario visita la página principal (
Home.js
) y ve la barra de búsqueda (SearchBar.js
). - El usuario introduce una consulta en la barra de búsqueda y envía la búsqueda.
SearchBar.js
llama a la funciónsearchBooks
del archivogoogleBooksApi.js
para buscar libros.googleBooksApi.js
devuelve los resultados de la búsqueda aSearchBar.js
.SearchBar.js
pasa los resultados al componente padreHome.js
a través de la funciónonSearchResults
.Home.js
actualiza su estado con los resultados de la búsqueda y renderiza la lista de libros utilizando el componenteBookCard.js
.- El usuario puede hacer clic en un libro para ver más detalles, lo que navega a la ruta
/book/:bookId
y renderiza el componenteBookPage.js
. BookPage.js
obtiene la información detallada del libro utilizando la funcióngetBookById
engoogleBooksApi.js
y renderiza el componenteBookDetails.js
con la información del libro.
Para crear archivos CSS para cada componente y página, sigue estos pasos:
Crea una carpeta llamada
styles
en la raíz de tu proyecto, si aún no la tienes.Dentro de la carpeta
styles
, crea archivos CSS separados para cada componente y página. Por ejemplo:searchBar.css
bookCard.css
bookDetails.css
home.css
bookPage.css
Añade estilos personalizados en cada archivo CSS según las necesidades de diseño y apariencia de tus componentes y páginas.
Importa los archivos CSS en los respectivos componentes y páginas. Por ejemplo, en
SearchBar.js
, añade la siguiente línea en la parte superior del archivo:
javascriptimport './styles/searchBar.css';
Repite este proceso para cada componente y página, importando su archivo CSS correspondiente.
Siguiendo estos pasos, tendrás un archivo CSS separado para cada componente y página, lo que facilita la organización y el mantenimiento de los estilos en tu proyecto.
Para utilizar Bootstrap en tu proyecto y aplicar un diseño adecuado a cada archivo CSS, sigue estos pasos:
- Asegúrate de tener Bootstrap instalado en tu proyecto. Si aún no lo has hecho, ejecuta el siguiente comando en tu terminal:
bashnpm install bootstrap
- Importa Bootstrap en tu archivo
src/index.js
:
javascriptimport 'bootstrap/dist/css/bootstrap.min.css';
A continuación, se proporcionan ejemplos de cómo aplicar estilos Bootstrap en cada archivo CSS para los componentes y páginas.
searchBar.css
css.search-container {
margin: 1rem 0;
}
.search-input {
width: 80%;
}
.search-button {
width: 20%;
}
bookCard.css
css.book-card {
margin: 1rem;
padding: 1rem;
border: 1px solid #dee2e6;
border-radius: 0.25rem;
}
.book-title {
font-size: 1.25rem;
margin-bottom: 0.5rem;
}
.book-authors {
font-size: 1rem;
color: #6c757d;
}
bookDetails.css
css.book-details {
margin: 1rem;
padding: 1rem;
border: 1px solid #dee2e6;
border-radius: 0.25rem;
}
.book-title {
font-size: 1.5rem;
margin-bottom: 0.5rem;
}
.book-authors {
font-size: 1.25rem;
color: #6c757d;
margin-bottom: 1rem;
}
.book-description {
font-size: 1rem;
}
home.css
css.books-container {
display: flex;
flex-wrap: wrap;
}
bookPage.css
css.book-page-container {
margin: 1rem;
}
Después de aplicar estos estilos, asegúrate de actualizar los componentes y las páginas para que utilicen las clases de Bootstrap y las clases personalizadas que hemos creado. Por ejemplo, en SearchBar.js
, puedes actualizar el componente para que utilice las clases:
javascriptimport React, { useState } from 'react';
import { searchBooks } from './services/googleBooksApi';
import './styles/searchBar.css';
const SearchBar = ({ onSearchResults }) => {
const [searchQuery, setSearchQuery] = useState('');
const handleSearch = async (event) => {
event.preventDefault();
const results = await searchBooks(searchQuery);
onSearchResults(results);
};
return (
<form onSubmit={handleSearch} className="search-container">
<input
type="text"
value={searchQuery}
onChange={(event) => setSearchQuery(event.target.value)}
placeholder="Buscar libros..."
className="form-control search-input"
/>
<button type="submit" className="btn btn-primary search-button">
Buscar
</button>
</form>
);
};
export default SearchBar;
Repita este proceso para todos los componentes y páginas, asegurándote de que utilicen las clases de Bootstrap y las clases personalizadas que hemos creado en los archivos CSS.
Aquí tienes cómo aplicar las clases de Bootstrap y las clases personalizadas en todos los componentes y páginas:
SearchBar.js
javascriptimport React, { useState } from 'react';
import { searchBooks } from './services/googleBooksApi';
import './styles/searchBar.css';
const SearchBar = ({ onSearchResults }) => {
const [searchQuery, setSearchQuery] = useState('');
const handleSearch = async (event) => {
event.preventDefault();
const results = await searchBooks(searchQuery);
onSearchResults(results);
};
return (
<form onSubmit={handleSearch} className="search-container">
<input
type="text"
value={searchQuery}
onChange={(event) => setSearchQuery(event.target.value)}
placeholder="Buscar libros..."
className="form-control search-input"
/>
<button type="submit" className="btn btn-primary search-button">
Buscar
</button>
</form>
);
};
export default SearchBar;
BookCard.js
javascriptimport React from 'react';
import { Link } from 'react-router-dom';
import './styles/bookCard.css';
const BookCard = ({ book }) => {
const { id, volumeInfo } = book;
const { title, authors, imageLinks } = volumeInfo;
const thumbnail = imageLinks?.thumbnail || '';
return (
<div className="book-card col-md-4">
<Link to={`/book/${id}`}>
<img src={thumbnail} alt={title} className="img-thumbnail" />
<h2 className="book-title">{title}</h2>
<p className="book-authors">{authors?.join(', ')}</p>
</Link>
</div>
);
};
export default BookCard;
BookDetails.js
javascriptimport React from 'react';
import './styles/bookDetails.css';
const BookDetails = ({ book }) => {
const { volumeInfo } = book;
const { title, authors, description } = volumeInfo;
return (
<div className="book-details">
<h1 className="book-title">{title}</h1>
<h2 className="book-authors">{authors?.join(', ')}</h2>
<p className="book-description">{description}</p>
</div>
);
};
export default BookDetails;
Home.js
javascriptimport React, { useState } from 'react';
import SearchBar from './components/SearchBar';
import BookCard from './components/BookCard';
import './styles/home.css';
const Home = () => {
const [books, setBooks] = useState([]);
const handleSearchResults = (results) => {
setBooks(results);
};
return (
<div>
<SearchBar onSearchResults={handleSearchResults} />
<div className="books-container row">
{books.map((book) => (
<BookCard key={book.id} book={book} />
))}
</div>
</div>
);
};
export default Home;
BookPage.js
javascriptimport React, { useState, useEffect } from 'react';
import { useParams } from 'react-router-dom';
import { getBookById } from './services/googleBooksApi';
import BookDetails from './components/BookDetails';
import './styles/bookPage.css';
const BookPage = () => {
const { bookId } = useParams();
const [book, setBook] = useState(null);
useEffect(() => {
const fetchBook = async () => {
const bookData = await getBookById(bookId);
setBook(book
scss (bookData);
};
fetchBook();
}, [bookId]);
return ( <div className="book-page-container"> {book ? <BookDetails book={book} /> : <p>Cargando detalles del libro...</p>} </div> ); };
export default BookPage;
css
Con estos cambios, ahora todos los componentes y páginas utilizan clases de Bootstrap y las clases personalizadas que hemos creado en los archivos CSS. Este diseño proporcionará una apariencia y sensación coherente y agradable en toda la aplicación. Asegúrate de ajustar los estilos según tus preferencias y necesidades específicas.
Para crear y configurar un service worker en src/serviceWorkerRegistration.js
, sigue estos pasos:
- Si no tienes la dependencia
workbox-webpack-plugin
, instálala ejecutando el siguiente comando:
bashnpm install workbox-webpack-plugin --save-dev
- Modifica tu archivo
webpack.config.js
para incluir el pluginworkbox-webpack-plugin
. Añade estas líneas al inicio del archivo:
javascriptconst { InjectManifest } = require('workbox-webpack-plugin');
- Añade la instancia del plugin
InjectManifest
dentro de la secciónplugins
del archivowebpack.config.js
:
javascriptplugins: [
// ...
new InjectManifest({
swSrc: './src/src-sw.js',
swDest: 'service-worker.js',
}),
// ...
],
- Crea un archivo llamado
src-sw.js
en la carpetasrc
. Este archivo será el service worker que se generará automáticamente. Añade el siguiente contenido:
javascriptimport { precacheAndRoute } from 'workbox-precaching';
// Precache assets y establecer rutas
precacheAndRoute(self.__WB_MANIFEST);
// Agregar otras estrategias de workbox aquí (si es necesario)
- Crea un archivo llamado
serviceWorkerRegistration.js
en la carpetasrc
. Añade el siguiente contenido:
javascriptconst isLocalhost = Boolean(
window.location.hostname === 'localhost' ||
window.location.hostname === '[::1]' ||
window.location.hostname.match(
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
)
);
export function register(config) {
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
if (publicUrl.origin !== window.location.origin) {
return;
}
window.addEventListener('load', () => {
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
if (isLocalhost) {
checkValidServiceWorker(swUrl, config);
navigator.serviceWorker.ready.then(() => {
console.log('This web app is being served cache-first by a service worker.');
});
} else {
registerValidSW(swUrl, config);
}
});
}
}
function registerValidSW(swUrl, config) {
navigator.serviceWorker
.register(swUrl)
.then((registration) => {
registration.onupdatefound = () => {
const installingWorker = registration.installing;
if (installingWorker == null) {
return;
}
installingWorker.onstatechange = () => {
if (installingWorker.state === 'installed') {
if (navigator.serviceWorker.controller) {
if (config && config.onUpdate) {
config.onUpdate(registration);
}
} else {
console.log('Content is cached for offline use.');
if (config && config.onSuccess) {
config.onSuccess(registration);
}
}
}
};
};
})
.catch((error) => {
console.error('Error during service worker registration:', error);
});
}
function checkValidServiceWorker(swUrl, config) {
fetch(swUrl, {
headers: { 'Service-Worker': 'script' },
})
.then((response) => {
const contentType = response.headers.get('content-type');
if (
response.status