La implementación de un sistema de autenticación y autorización es un componente fundamental en el desarrollo de aplicaciones web seguras. FastAPI, como framework de desarrollo web moderno y rápido, facilita este proceso a través de herramientas integradas que permiten gestionar la autenticación de usuarios de forma eficiente. En este capítulo, exploraremos cómo construir un sistema de autenticación basado en JWT (JSON Web Tokens), usando FastAPI junto con otras bibliotecas como passlib para el manejo de contraseñas y PyJWT para la creación y validación de tokens.

Para comenzar, es necesario estructurar una clase denominada AuthHandler, que integrará todas las funcionalidades de autenticación y autorización. Esta clase gestionará el proceso de encriptación de contraseñas, creación de tokens y validación de accesos a rutas protegidas. A continuación, detallamos cómo configurar y estructurar esta clase paso a paso.

Creación de la clase AuthHandler

Para la clase AuthHandler, se importan varias dependencias necesarias. Entre ellas, se encuentran datetime, jwt, y las herramientas de seguridad de FastAPI como HTTPBearer y HTTPAuthorizationCredentials. Además, se utiliza CryptContext de passlib para gestionar el procesamiento de contraseñas de forma segura.

python
import datetime
import jwt from fastapi import HTTPException, Security from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer from passlib.context import CryptContext class AuthHandler: security = HTTPBearer() pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") secret = "FARMSTACKsecretString"

La propiedad security usa el esquema HTTPBearer de FastAPI, lo que facilita la inclusión de tokens de acceso en las cabeceras HTTP de las solicitudes. La propiedad pwd_context define el contexto para encriptar las contraseñas, utilizando el algoritmo bcrypt. Finalmente, secret es una clave secreta usada para firmar los tokens. Aunque en un entorno de producción esta clave debe generarse aleatoriamente y almacenarse en una variable de entorno, en este ejemplo se encuentra hardcodeada por simplicidad.

Hashing de contraseñas

La creación de contraseñas seguras y su almacenamiento adecuado es crucial. Para ello, la clase AuthHandler incluye un método para generar el hash de una contraseña. Este hash será lo que realmente se almacene en la base de datos en lugar de la contraseña en texto claro.

python
def get_password_hash(self, password: str) -> str:
return self.pwd_context.hash(password)

Este método recibe la contraseña en texto claro y devuelve su versión hasheada, que puede ser almacenada de manera segura en la base de datos.

Verificación de contraseñas

Es necesario verificar si una contraseña proporcionada coincide con su versión hasheada almacenada. Para ello, la clase AuthHandler tiene otro método que compara la contraseña en texto claro con el hash almacenado.

python
def verify_password(self, plain_password: str, hashed_password: str) -> bool: return self.pwd_context.verify(plain_password, hashed_password)

Este método devuelve un valor booleano que indica si las contraseñas coinciden.

Creación y codificación de tokens

La creación de tokens es otro aspecto esencial del proceso de autenticación. Los tokens JWT contienen información importante sobre el usuario y su sesión, y deben ser firmados con una clave secreta para garantizar su integridad. La clase AuthHandler incluye un método que genera un JWT con los datos del usuario.

python
def encode_token(self, user_id: int, username: str) -> str: payload = { "exp": datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(minutes=30), "iat": datetime.datetime.now(datetime.timezone.utc), "sub": {"user_id": user_id, "username": username}, } return jwt.encode(payload, self.secret, algorithm="HS256")

Este método construye el payload del token, que incluye la fecha de expiración (exp), el tiempo de emisión (iat), y los datos del usuario (como su ID y nombre de usuario). La clave secreta se usa para firmar el token con el algoritmo HS256.

Decodificación de tokens

Para validar los accesos, es necesario decodificar los tokens enviados en las solicitudes. Si el token es válido y no ha expirado, se devolverá la información del usuario; de lo contrario, se lanzará una excepción.

python
def decode_token(self, token: str): try: payload = jwt.decode(token, self.secret, algorithms=["HS256"]) return payload["sub"] except jwt.ExpiredSignatureError: raise HTTPException(status_code=401, detail="Signature has expired") except jwt.InvalidTokenError: raise HTTPException(status_code=401, detail="Invalid token")

Este método intenta decodificar el token. Si el token ha expirado o es inválido, se lanza una excepción HTTP con el mensaje correspondiente.

Middleware de autorización

El siguiente paso es definir una dependencia de autorización que se utilizará en las rutas protegidas. Esta dependencia verifica que el token enviado en la cabecera de la solicitud sea válido.

python
def auth_wrapper(self, auth: HTTPAuthorizationCredentials = Security(security)) -> dict:
return self.decode_token(auth.credentials)

Este auth_wrapper se utiliza para proteger las rutas que requieren autenticación, asegurándose de que los usuarios solo accedan a ellas si tienen un token válido.

Creación de rutas para registro y login

Una vez que la clase AuthHandler está configurada, el siguiente paso es crear las rutas para el registro de nuevos usuarios y el inicio de sesión. Estas rutas estarán protegidas por el sistema de autenticación basado en JWT.

python
import json import uuid from fastapi import APIRouter, Body, Depends, HTTPException, Request from fastapi.encoders import jsonable_encoder from fastapi.responses import JSONResponse from authentication import AuthHandler from models import UserBase, UserIn, UserOut, UsersList router = APIRouter() auth_handler = AuthHandler() @router.post("/register", response_description="Register user")
async def register(request: Request, newUser: UserIn = Body(...)) -> UserBase:
users = json.loads(
open("users.json").read())["users"] newUser.password = auth_handler.get_password_hash(newUser.password) if any(user["username"] == newUser.username for user in users): raise HTTPException(status_code=409, detail="Username already taken") newUser = jsonable_encoder(newUser) newUser["id"] = str(uuid.uuid4()) users.append(newUser)
with open("users.json", "w") as f:
json.dump({
"users": users}, f, indent=4) return newUser

En este ejemplo, se utiliza un archivo JSON como base de datos temporal para almacenar los usuarios registrados. Este enfoque es útil para pruebas y prototipos, pero en una aplicación real se debe usar una base de datos adecuada.

Es importante recordar que este código es un ejemplo básico. En un entorno de producción, se deben implementar prácticas adicionales para garantizar la seguridad, como la validación de entradas y la protección contra ataques de inyección o XSS. Además, la clave secreta (secret) debe generarse de manera aleatoria y almacenarse en un entorno seguro, no directamente en el código.

¿Cómo implementar un sistema de autenticación básico con FastAPI y manejar usuarios en una aplicación web?

La creación de un sistema de autenticación desde cero puede parecer desafiante al principio, pero con herramientas como FastAPI, el proceso se vuelve mucho más accesible. A continuación, se describe cómo implementar un sistema de autenticación básico utilizando FastAPI, que incluye la creación de un endpoint de registro, un endpoint de inicio de sesión y la protección de rutas con autenticación mediante tokens JWT.

Al comenzar a construir el sistema de autenticación, primero es necesario importar las bibliotecas y paquetes adecuados. A pesar de que algunos de estos, como JSON y uuid, no serán necesarios en una base de datos MongoDB real, son útiles cuando se trabaja con una estructura de datos más simple, como un archivo JSON.

En primer lugar, se crea un enrutador APIRouter y una clase personalizada AuthHandler que maneja la autenticación y autorización. La clase AuthHandler tiene la tarea de gestionar el hash de contraseñas y la generación de tokens JWT para los usuarios. El primer endpoint que se debe implementar es el de registro (/register). Este recibe los datos del nuevo usuario y los valida usando una clase UserIn definida en el archivo models.py, mientras que la salida se ajusta al formato de la clase UserBase. Es importante tener en cuenta que, aunque se devuelve el usuario recién creado con todos sus datos, en un sistema real no se debería devolver la contraseña hasheada ni ningún otro dato sensible a los usuarios.

Para simular una base de datos en este ejemplo, se utiliza un archivo JSON llamado users.json, que almacena los usuarios como una lista de diccionarios. Cada diccionario contiene el nombre de usuario, la contraseña hasheada y un identificador único (UUID). El flujo de registro implica verificar que el nombre de usuario no esté ya en uso y, en caso de que no se repita, proceder a almacenar los datos del usuario, incluyendo el hash de su contraseña. Es clave emplear el método jsonable_encoder para convertir el diccionario en un formato adecuado para almacenarlo.

Una vez que los usuarios se registran, es hora de implementar el endpoint de inicio de sesión (/login). Este endpoint se encarga de verificar las credenciales proporcionadas por el usuario. Si el nombre de usuario y la contraseña coinciden con los datos almacenados, el sistema genera un token JWT que se devolverá al usuario. La verificación de la contraseña debe realizarse sin especificar si el error proviene del nombre de usuario o de la contraseña incorrecta, ya que esto aumenta la seguridad del sistema.

Una vez que se han implementado ambos endpoints, es necesario conectar el enrutador a la aplicación FastAPI en el archivo principal (app.py). Aquí se agrega middleware CORS para permitir la interacción con un posible frontend desarrollado en React u otro framework. Además, se incluye el enrutador de usuarios (users_router), lo que habilita las rutas relacionadas con el manejo de usuarios.

En cuanto a la prueba del sistema, se pueden utilizar herramientas como HTTPie para interactuar con la API. Por ejemplo, para registrar un nuevo usuario, se puede ejecutar un comando HTTPie en la terminal para hacer una solicitud POST a /users/register, proporcionando un nombre de usuario y una contraseña. Si la solicitud es exitosa, el servidor devolverá el usuario creado con un identificador UUID y una contraseña hasheada.

Al probar el inicio de sesión, el servidor devolverá un token JWT si las credenciales son correctas. Si las credenciales son incorrectas, se enviará una respuesta con un código de estado 401 y un mensaje de error indicando que las credenciales son inválidas. Este token es esencial para acceder a rutas protegidas dentro de la aplicación.

Una vez que el sistema básico de autenticación está en funcionamiento, se puede añadir una ruta protegida que solo esté disponible para los usuarios autenticados. Para ello, se utiliza la dependencia Depends(auth_handler.auth_wrapper), que asegura que el usuario esté autenticado antes de acceder a la ruta solicitada. En este caso, se crea un endpoint /list, que devuelve una lista de todos los usuarios registrados en la base de datos simulada.

En resumen, se ha construido un sistema de autenticación básico con FastAPI que incluye el registro de usuarios, el inicio de sesión con verificación de credenciales y la protección de rutas mediante tokens JWT. Este sistema es una base sólida sobre la cual se pueden agregar más características y funcionalidades, como la integración con una base de datos real o la implementación de roles y permisos de usuario más avanzados.

Para garantizar la seguridad en una implementación real, es fundamental entender que en un entorno de producción nunca se deben almacenar las contraseñas en texto claro ni utilizar un archivo JSON como base de datos. El uso de MongoDB u otro sistema de base de datos con medidas adecuadas de seguridad es crucial. También es importante utilizar bibliotecas robustas y actualizadas para la gestión de autenticación y tokens, y seguir buenas prácticas en cuanto a la protección de datos sensibles.

Además, siempre se debe validar adecuadamente cualquier entrada proveniente del usuario para evitar vulnerabilidades como inyecciones SQL o ataques de tipo Cross-Site Scripting (XSS). El manejo de errores debe ser claro y controlado para no exponer detalles internos del sistema.

¿Cómo configurar y gestionar rutas con React Router en una aplicación Vite?

En el desarrollo de aplicaciones modernas con React, uno de los aspectos fundamentales es la gestión eficiente de las rutas y la navegación entre distintas vistas sin necesidad de recargar la página. React Router, una de las bibliotecas más populares para la gestión de rutas en React, permite lograr esto mediante un enrutamiento declarativo. A continuación, describimos los pasos necesarios para configurar el enrutador en una aplicación React utilizando Vite y cómo manejar situaciones como rutas no definidas, layouts y carga de datos.

Al comenzar, se deben importar las funciones necesarias de react-router-dom, como createBrowserRouter, Route, y RouterProvider. Una vez que se definen las rutas con createRoutesFromElements, estas se pueden asociar a componentes específicos, tales como páginas de la aplicación. En este caso, se crea un enrutador que asocia varias rutas a diferentes componentes: Home, Cars, Login, y NewCar, entre otros.

El código básico para crear el enrutador se ve de la siguiente manera:

jsx
import { createBrowserRouter, createRoutesFromElements, RouterProvider } from "react-router-dom";
import RootLayout from "./layouts/RootLayout"; import Cars from "./pages/Cars"; import Home from "./pages/Home"; import Login from "./pages/Login"; import NewCar from "./pages/NewCar";
import SingleCar from "./pages/SingleCar";
const router = createBrowserRouter( createRoutesFromElements( <Route path="/" element={<RootLayout />}> <Route index element={<Home />} /> <Route path="cars" element={<Cars />} /> <Route path="login" element={<Login />} /> <Route path="new-car" element={<NewCar />} /> <Route path="cars/:carId" element={<SingleCar />} /> </Route> ) );
export default function App() {
return <RouterProvider router={router} />; }

Al ejecutar la aplicación, al acceder a la dirección http://localhost:5173, aparecerá el contenido correspondiente a RootLayout. Sin embargo, si se navega a rutas no definidas, como /about, se mostrará un error de tipo 404, lo que confirma que el enrutador está funcionando correctamente. Este comportamiento, aunque útil, puede mejorarse para que la aplicación maneje de manera adecuada las rutas no encontradas.

Para manejar rutas no definidas, es necesario crear una página especial, denominada NotFound.jsx, donde se indicará que la página no existe:

jsx
const NotFound = () => {
return ( <div> ¡Esta página aún no existe! </div> ); }; export default NotFound;

Posteriormente, se debe modificar el archivo de rutas para agregar una ruta que capture todas las URL no definidas utilizando el carácter comodín *, lo que asegurará que cualquier ruta no definida muestre la página de "No encontrado". Este cambio se realiza agregando la ruta de NotFound al final de las rutas existentes en el archivo App.jsx:

jsx
<Route path="*" element={<NotFound />} />

Con esto, cualquier intento de navegación a una ruta no válida mostrará la página de error. Sin embargo, la funcionalidad principal del enrutador es mostrar los componentes correspondientes de acuerdo con la ruta definida, sin necesidad de recargar la página. Para hacer esto, se utiliza el componente Outlet, el cual se debe incluir en el layout principal (RootLayout.jsx) para que las rutas anidadas puedan renderizarse correctamente.

El componente RootLayout.jsx modificado incluiría lo siguiente:

jsx
import { Outlet } from "react-router-dom"; const RootLayout = () => { return ( <div> <h1>RootLayout</h1> <Outlet /> </div> ); }; export default RootLayout;

Con la inclusión del Outlet, la página se actualizará dinámicamente conforme se navega entre las rutas definidas. Además, se puede añadir navegación dentro del RootLayout mediante los componentes NavLink, lo que permite al usuario moverse entre las distintas páginas de la aplicación sin recargarla.

jsx
import { NavLink } from "react-router-dom"; const RootLayout = () => { return ( <div> <h1>RootLayout</h1> <nav>
<NavLink to="/">Home</NavLink>
<NavLink to="/cars">Cars</NavLink>
<NavLink to="/login">Login</NavLink>
<NavLink to="/new-car">New Car</NavLink> </nav> <Outlet /> </div> ); };

Además de las rutas y los layouts, una de las características poderosas de React Router son los loaders (cargadores). Estos permiten cargar datos necesarios para los componentes antes de que se rendericen, lo que puede mejorar la eficiencia de la aplicación al evitar solicitudes de datos innecesarias después de que la página ya ha sido cargada. Para usar un loader, se debe definir un archivo .env para configurar la URL del backend, y luego utilizar el hook useLoader para obtener datos de manera eficiente.

Por ejemplo, en el componente Cars.jsx, se puede configurar un loader que obtenga información de una API backend antes de que el componente se renderice:

jsx
const Cars = () => {
const { cars } = useLoader(async () => { const response = await fetch(`${import.meta.env.VITE_API_URL}/cars`); return response.json(); }); return ( <div> {cars.map(car => (
<CarCard key={car.id} car={car} />
))}
</div> ); };

Los loaders proporcionan una manera sencilla de gestionar la carga de datos, optimizando la experiencia del usuario y asegurando que los datos se estén recibiendo en el momento adecuado.

Con todo esto, React Router permite construir aplicaciones web dinámicas y escalables, que cargan solo lo necesario, mejoran la navegación y optimizan la experiencia del usuario. El uso adecuado de rutas anidadas, layouts y loaders es esencial para aprovechar al máximo las capacidades de React Router y Vite.

¿Cómo gestionar la autenticación y protección de rutas en una aplicación React?

La implementación de un sistema de autenticación en una aplicación React es crucial para garantizar que solo los usuarios autorizados tengan acceso a ciertos recursos o funcionalidades. En esta sección se exploran los pasos básicos para configurar un contexto de autenticación, manejar las funciones de inicio de sesión y cierre de sesión, y proteger las rutas que requieren un usuario autenticado.

El proceso comienza con la creación de un proveedor de contexto que maneje los estados de usuario y el JWT (JSON Web Token). Al configurar el contexto, se facilita el acceso a estos valores desde cualquier componente hijo. La estructura básica para gestionar el inicio de sesión es la siguiente: si el usuario está autenticado, se actualizan las variables del contexto con su nombre de usuario y el JWT; si no, estas variables se restablecen a null.

El siguiente paso es implementar la función de cierre de sesión, que restablece el estado del usuario, elimina el JWT del almacenamiento local del navegador y, opcionalmente, muestra un mensaje de confirmación de cierre de sesión exitoso. De esta forma, el componente AuthProvider maneja tanto la autenticación como el cierre de sesión.

La parte crucial del sistema de autenticación es el uso del hook useAuth, que simplifica el acceso a los valores del contexto en cualquier componente que lo necesite. Este hook garantiza que cualquier intento de acceder al contexto fuera del proveedor AuthProvider lance un error, asegurando que solo los componentes dentro de su alcance puedan interactuar con el contexto de autenticación.

Una vez configurado el sistema de contexto, el siguiente paso es proteger las rutas de la aplicación. Algunas páginas requieren que el usuario esté autenticado antes de acceder a ellas. Para ello, React Router permite crear rutas protegidas mediante componentes de orden superior, como el componente AuthRequired. Este componente verifica si el JWT está presente, y en caso de no estarlo, redirige al usuario a la página de inicio de sesión. Si el JWT es válido, permite el acceso a las rutas protegidas a través de Outlet.

La protección de rutas es esencial para aplicaciones que manejan información sensible o funcionalidades exclusivas para usuarios autenticados, como la creación de nuevos recursos o la visualización de información personal. Con este enfoque, se asegura que solo los usuarios con las credenciales correctas puedan realizar operaciones específicas, lo que mejora la seguridad y la experiencia del usuario.

Por lo tanto, una vez configurado el contexto de autenticación y las funciones de inicio/cierre de sesión, y creado el sistema de rutas protegidas, el sistema de autenticación estará listo para ser utilizado en toda la aplicación. Es importante tener en cuenta que la protección de rutas no debe ser el único mecanismo de seguridad; siempre es recomendable verificar el JWT en el servidor para garantizar que no haya sido manipulado o que haya expirado. Además, en el caso de que el JWT se elimine o se invalide en el cliente (por ejemplo, al cerrar sesión), es crucial que la aplicación se redirija de manera apropiada a una página de inicio de sesión o de acceso restringido.

Además de la protección de rutas, también es relevante entender cómo gestionar el flujo de inicio de sesión y cierre de sesión de manera fluida, garantizando una experiencia de usuario coherente. La visualización de mensajes de estado, como "Inicio de sesión exitoso" o "Cierre de sesión exitoso", proporciona retroalimentación valiosa al usuario, lo que mejora la interacción con la aplicación.

¿Cómo integrar tareas en segundo plano con FastAPI para mejorar la experiencia de usuario?

El desarrollo de aplicaciones web modernas requiere un enfoque efectivo para manejar tareas que puedan consumir tiempo y que no necesariamente deban ser procesadas de inmediato, como el envío de correos electrónicos o la interacción con servicios externos. FastAPI ofrece una excelente solución para esto a través de su manejo de tareas en segundo plano, permitiendo que la aplicación no se bloquee mientras ejecuta procesos largos.

En FastAPI, se utilizan tareas en segundo plano para manejar aquellas operaciones que deberían ejecutarse de manera asíncrona después de que la respuesta ya haya sido enviada al cliente. Esto es fundamental para mantener una experiencia de usuario fluida, ya que cualquier operación que requiera esperar una respuesta, como la consulta a una API externa o la creación de un documento complejo basado en datos, podría afectar negativamente al tiempo de respuesta del servidor si se maneja de forma sincrónica.

Por ejemplo, en un contexto donde se está desarrollando un sistema de venta de autos, el proceso de agregar un nuevo auto con una foto podría implicar interacciones con servicios de terceros, como una plataforma de almacenamiento en la nube. Para evitar que el usuario tenga que esperar a que todos estos procesos se completen antes de recibir una respuesta, se puede utilizar una tarea en segundo plano para subir la imagen y almacenar la URL en la base de datos de MongoDB, mientras que el usuario ya recibe una confirmación inmediata de que el auto ha sido agregado a la base de datos.

Un ejemplo de cómo crear una API con FastAPI para agregar un auto nuevo con su respectiva imagen se muestra en el siguiente fragmento de código:

python
@router.post(
"/", response_description="Add new car with picture", response_model=Car, status_code=status.HTTP_201_CREATED, ) async def add_car_with_picture( brand: str = Form("brand"), make: str = Form("make"), year: int = Form("year"), cm3: int = Form("cm3"), km: int = Form("km"), price: int = Form("price"), picture: UploadFile = File("picture"), user_data=Depends(auth_handler.auth_wrapper), ): cloudinary_image = cloudinary.uploader.upload( picture.file, folder="FARM2", crop="fill", width=800, gravity="auto" ) picture_url = cloudinary_image["url"] user = await User.get(user_data["user_id"]) car = Car( brand=brand, make=make, year=year, cm3=cm3, km=km, price=price, picture_url=picture_url, user=user, ) return await car.insert(link_rule=WriteRules.WRITE)

Este código muestra cómo integrar una plataforma externa (Cloudinary) para subir imágenes de forma eficiente mientras la aplicación sigue funcionando sin interrupciones. El proceso de subir la foto y obtener la URL se maneja en segundo plano, permitiendo que el usuario reciba una respuesta rápida.

Además, FastAPI permite integrar tareas en segundo plano de forma intuitiva mediante la clase BackgroundTasks, que facilita la ejecución de funciones después de enviar la respuesta. A continuación, se muestra cómo implementarlas en un proceso de inicio de sesión de un usuario:

python
from fastapi import APIRouter, BackgroundTasks, Body, Depends, HTTPException from background import delayed_task @router.post("/login", response_description="Login user and return token") async def login(background_tasks: BackgroundTasks, loginUser: LoginUser = Body(...)) -> str: user = await User.find_one(User.username == loginUser.username) if not user: raise HTTPException(status_code=404, detail="User not found") # Ejecutando la tarea en segundo plano background_tasks.add_task(delayed_task, loginUser.username) # Lógica de autenticación token = create_token(user) return token

En este caso, una tarea en segundo plano (delayed_task) se activa cuando un usuario inicia sesión. Este tipo de tareas puede servir para realizar acciones como la actualización de estadísticas de usuarios o la ejecución de procesos de análisis sin afectar el flujo principal de la aplicación.

Es importante resaltar que, aunque las tareas en segundo plano son muy útiles para operaciones simples, no son adecuadas para procesos que requieran un procesamiento intensivo o multitarea, ya que pueden poner en riesgo el rendimiento de la aplicación. En situaciones donde se necesite realizar tareas más pesadas o escalables, herramientas como Celery se convierten en una opción más robusta y adecuada.

Por otro lado, FastAPI proporciona la posibilidad de configurar los routers de manera sencilla, como se muestra a continuación:

python
from fastapi import FastAPI
from routers import cars as cars_router, user as user_router from fastapi_cors import CORS app = FastAPI() # Integrando routers app.include_router(cars_router.router, prefix="/cars", tags=["Cars"]) app.include_router(user_router.router, prefix="/users", tags=["Users"])

Esta integración modular permite estructurar la aplicación de manera eficiente y escalable, facilitando la separación de responsabilidades entre distintas partes del sistema.

Al usar FastAPI y Beanie, puedes construir una aplicación backend eficiente y rápida, con funcionalidades que van desde la creación de recursos hasta la integración con servicios de terceros. Además, la capacidad de gestionar tareas en segundo plano garantiza que los usuarios no tengan que esperar por procesos largos, lo cual mejora notablemente la experiencia de usuario.

Es importante también que, al utilizar tareas en segundo plano, los desarrolladores gestionen correctamente las excepciones y errores, asegurándose de que, si alguna tarea falla, el sistema pueda manejarla adecuadamente sin afectar la experiencia general del usuario. La implementación de un sistema de notificación de fallos o la utilización de un sistema de monitoreo de tareas en segundo plano puede ser crucial en aplicaciones en producción.