FakeStore Fase3 Mandrade
Un paquete Flutter educativo y ligero para interactuar con la Fake Store API. Refactorizado a un estilo minimalista: DataSource + Repositorios + Modelos (sin fachada).
✨ Características
✅ Clean Architecture - Código mantenible y testeable
✅ API Clara - DataSource + repositorios explícitos
✅ Completamente tipado - Modelos de dominio seguros con null safety
✅ Manejo de errores - Excepciones descriptivas
✅ Testing incluido - Suite completa de pruebas unitarias
✅ Documentación completa - Métodos y clases documentados
🛍️ API Endpoints
| Categoría | Endpoints | Descripción |
|---|---|---|
| Productos | getAllProducts() |
Lista completa de productos |
getProduct(id) |
Detalles de producto específico | |
getCategories() |
Todas las categorías | |
getProductsInCategory() |
Productos por categoría | |
| Usuarios | getAllUsers() |
Lista completa de usuarios |
getUser(id) |
Detalles de usuario específico | |
createUser(input) |
Crea nuevo usuario | |
login(username,password) |
Autentica y retorna token JWT | |
| Carritos | getAllCarts() |
Lista completa de carritos |
getCart(id) |
Detalles de carrito específico | |
getUserCarts(userId) |
Carritos de un usuario |
📦 Instalación
dependencies:
fakestore_fase3_mandrade: ^2.0.0
🚀 Uso Rápido (Nuevo Punto de Entrada)
import 'package:fakestore_fase3_mandrade/fakestore_fase3_mandrade.dart';
// 1. Data source (maneja HTTP y parsing)
final ds = FakeStoreRemoteDataSource();
// 2. Repos que necesites (inyectando el datasource)
final productsRepo = ProductRepositoryImpl(dataSource: ds);
final usersRepo = UserRepositoryImpl(dataSource: ds);
final cartsRepo = CartRepositoryImpl(dataSource: ds);
// 3. Operaciones
final products = await productsRepo.getAllProducts(limit: 5);
final user = await usersRepo.getUserById(1);
final carts = await cartsRepo.getAllCarts();
print(products.first.title);
print(user.name.fullName);
print(carts.length);
// No se requiere dispose explícito.
📊 Ejemplos Detallados
🛍️ Productos
final ds = FakeStoreRemoteDataSource();
final productsRepo = ProductRepositoryImpl(dataSource: ds);
final products = await productsRepo.getAllProducts(limit: 10, sort: 'desc');
final categories = await productsRepo.getCategories();
final electronics = await productsRepo.getProductsByCategory('electronics');
final product = await productsRepo.getProductById(1);
print('${product.title} - \$${product.price}');
print('Rating: ${product.rating.rate}/5 (${product.rating.count} reviews)');
// No se requiere dispose.
👥 Usuarios
final ds = FakeStoreRemoteDataSource();
final usersRepo = UserRepositoryImpl(dataSource: ds);
final users = await usersRepo.getAllUsers(limit: 5);
final user = await usersRepo.getUserById(1);
print('${user.name.firstName} ${user.name.lastName}');
print('Email: ${user.email}');
print('Ciudad: ${user.address.city}');
// No se requiere dispose.
// Crear nuevo usuario
final nuevo = await usersRepo.createUser(
CreateUserInput.minimal(
email: 'new@example.com',
username: 'newuser',
password: 'Secret123',
),
);
print('Usuario creado id=${nuevo.id}');
// Login (autenticación)
final token = await usersRepo.login('mor_2314', '83r5^_');
print('Token: $token');
🛒 Carritos
final ds = FakeStoreRemoteDataSource();
final cartsRepo = CartRepositoryImpl(dataSource: ds);
final carts = await cartsRepo.getAllCarts();
final cart = await cartsRepo.getCartById(1);
final userCarts = await cartsRepo.getCartsByUserId(1);
print('Carrito del usuario: ${cart.userId}');
print('Total productos: ${cart.totalProducts}');
for (final p in cart.products) {
print('Producto ${p.productId}: ${p.quantity} unidades');
}
🏛️ Arquitectura
La librería sigue un modelo deliberadamente minimalista para que entiendas rápido el flujo sin capas "ceremoniales".
Flujo de datos (request → modelo):
HTTP (Fake Store API)
↓
FakeStoreRemoteDataSource (GET + decode JSON)
↓
*RepositoryImpl (adaptaciones ligeras / consistencia)
↓
Tu código (UI, Controlador, UseCase propio, etc.)
Capas incluidas
- Data Source (
FakeStoreRemoteDataSource):- Realiza peticiones HTTP.
- Decodifica JSON y delega a los mappers.
- Inyectable con
http.Clientpersonalizado (para logging, retry, etc.).
- Repositorios (
ProductRepositoryImpl,UserRepositoryImpl,CartRepositoryImpl):- Proveen una interfaz clara y estable.
- Pueden ser decorados (caching, logging) sin tocar el data source.
- Modelos + Mappers: Estructuras inmutables con conversión segura desde el JSON de la API.
Capas que NO se incluyen (y por qué)
| Capa | Motivo de ausencia | Cuándo agregarla tú |
|---|---|---|
| Use Cases | Los métodos del repo ya son atómicos y claros. | Si necesitas orquestar múltiples repos o aplicar reglas. |
| Facade/Service | Sería solo un pass-through adicional. | Si expones un SDK unificado interno para tu app. |
| Data Local Cache | Evita complejidad inicial. | Si tu app necesita offline o reducir llamadas. |
Principios aplicados
- KISS / YAGNI: Solo lo necesario para consumir la API productivamente.
- SRP: DataSource (transport + parse) y Repos (contrato semántico) separados.
- Open/Closed: Puedes envolver repos o datasource con decorators sin modificar su código.
Ejemplos de extensión
- Caching ligero:
class CachedProductRepository implements ProductRepository {
final ProductRepository _inner;
List<ProductModel>? _cache;
DateTime? _lastFetch;
CachedProductRepository(this._inner);
@override
Future<List<ProductModel>> getAllProducts({int? limit, String? sort}) async {
final fresh = _lastFetch != null &&
DateTime.now().difference(_lastFetch!) < const Duration(minutes: 5);
if (_cache != null && fresh) return _cache!;
final data = await _inner.getAllProducts(limit: limit, sort: sort);
_cache = data; _lastFetch = DateTime.now();
return data;
}
// Demás métodos delegan ...
@override Future<ProductModel> getProductById(int id) => _inner.getProductById(id);
@override Future<List<String>> getCategories() => _inner.getCategories();
@override Future<List<ProductModel>> getProductsByCategory(String c,{int? limit,String? sort}) =>
_inner.getProductsByCategory(c, limit: limit, sort: sort);
}
- Cliente/fachada opcional:
class FakeStoreClient {
final FakeStoreRemoteDataSource _ds = FakeStoreRemoteDataSource();
late final products = ProductRepositoryImpl(dataSource: _ds);
late final users = UserRepositoryImpl(dataSource: _ds);
late final carts = CartRepositoryImpl(dataSource: _ds);
}
- Inyección de client con logging:
final loggingClient = LoggingClient(inner: http.Client());
final ds = FakeStoreRemoteDataSource(httpClient: loggingClient);
final repo = ProductRepositoryImpl(dataSource: ds);
¿Cuándo agregar más capas?
- Reglas de negocio compuestas → crea Use Cases.
- Reintentos / fallback de origen → decorator sobre DataSource o repos.
- Multi-backend (unir APIs) → una nueva capa de composición antes de la UI.
Con esto obtienes una base clara, extensible y fácil de leer desde el primer minuto.
�️ Mejora: Gestión de errores (Either / dartz)
Se ha mejorado el manejo de errores del paquete usando el tipo funcional Either proporcionado por la dependencia dartz.
- El
DataSourceahora captura excepciones (conectividad, parsing, HTTP) y retornaEither<Failure, T>en lugar de lanzarExceptioncrudas. - Se añadieron tipos de fallo (
Failure) específicos:ServerFailure,NetworkFailure,ParsingFailure,AuthenticationFailure,NotFoundFailure,ValidationFailure,UnknownFailure. - Los repositorios y casos de uso ahora propagan
Either, y la UI/Controladores deben usarfoldpara transformarLeft/Righten mensajes/acciones apropiadas.
Beneficios:
- Errores tipados y predecibles que facilitan el testing y el manejo en capas superiores.
- Permite componer operaciones y modelar flujos fallidos sin lanzar excepciones.
- Mejora la experiencia de usuario mostrando mensajes más concretos.
Ejemplo rápido:
final result = await productsRepo.getAllProducts();
result.fold(
(failure) => print('Error: ${failure.message}'),
(products) => print('Productos: ${products.length}'),
);
�📱 Aplicación de Ejemplo
Incluye una aplicación completa en example/ que demuestra todos los métodos del paquete con UI moderna y manejo de estados.
cd example
flutter pub get
flutter run
📚 Documentación
Creado por Marco Andrade - GitHub
Libraries
- data/datasources/fake_store_remote_datasource
- data/repositories/cart_repository_impl
- data/repositories/product_repository_impl
- data/repositories/user_repository_impl
- domain/failures/failures
- domain/mappers/cart_mappers
- domain/mappers/product_mappers
- domain/mappers/user_mappers
- domain/models/cart_model
- domain/models/create_user_input
- domain/models/product_model
- domain/models/user_model
- domain/repositories/cart_repository
- domain/repositories/product_repository
- domain/repositories/user_repository
- fakestore_fase3_mandrade
- Paquete Flutter para interactuar con la Fake Store API