Análisis y Desarrollo de Software
Taller Individual · Flutter · Proyecto "Café Místico"
Fase 0 · Requisitos Fase 1 · Proyecto y assets Fase 2 · Modelo y backend Fase 3 · Componentes Fase 4 · Pantallas ✅ Checklist
📱 TALLER INDIVIDUAL · ADSO · FASE 4 DE 5

Pantallas
del proyecto

La pantalla de bienvenida (home.dart) y la pantalla principal del menú (menu.dart), integrando todos los componentes construidos en la fase anterior y conectándolos al backend.

📅 Sesión del 27 de julio de 2026
☕ ecommer_app
🔒 Código protegido, solo lectura
🔒
Código de solo lectura: no se puede seleccionar, copiar ni pegar (tampoco desde el inspector del navegador). Debes escribirlo tú mismo, línea por línea, en tu editor.
En cada paso encontrarás: 💻 Código 🧪 Prueba 🔒 Sin copiar
4
FASE 4
Pantallas (pages/)
PASO 12
Pantalla de bienvenida (pages/home.dart)
💻 Código
Imagen de fondo, gradiente oscuro y navegación al menú:
dart🔒 import 'package:flutter/material.dart'; import 'package:ecommer_app/pages/menu.dart'; class HomePages extends StatelessWidget { const HomePages({super.key}); void _navigateToMenu(BuildContext context) { Navigator.pushReplacement( context, MaterialPageRoute(builder: (context) => const Menu()), ); } @override Widget build(BuildContext context) { return Scaffold( body: Stack( children: [ Positioned.fill( child: Image.asset('assets/img/fondo.avif', fit: BoxFit.cover, width: double.infinity, height: double.infinity), ), Positioned.fill( child: Container( decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [ Colors.black.withValues(alpha: 0.75), Colors.transparent, Colors.black.withValues(alpha: 0.85), ], stops: const [0.0, 0.45, 0.9], ), ), ), ),
Y el contenido principal encima (título, subtítulo y botón "EXPLORAR MENÚ"):
dart🔒 Positioned.fill( child: SafeArea( child: Padding( padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 30), child: Column( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Column( children: [ const SizedBox(height: 20), Text( 'Café Místico', textAlign: TextAlign.center, style: TextStyle( fontSize: 42, fontWeight: FontWeight.bold, color: const Color(0xFFF5E6D3), letterSpacing: 1.8, ), ), const SizedBox(height: 8), Text( 'EL MEJOR AROMA EN CADA GRANO', textAlign: TextAlign.center, style: TextStyle(fontSize: 12, color: Colors.white.withValues(alpha: 0.75), letterSpacing: 2.5), ), ], ), ElevatedButton( onPressed: () => _navigateToMenu(context), style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFFC88D51), foregroundColor: Colors.white, padding: const EdgeInsets.symmetric(horizontal: 40, vertical: 16), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(30)), ), child: const Row( mainAxisSize: MainAxisSize.min, children: [ Text('EXPLORAR MENÚ', style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold, letterSpacing: 1.5)), SizedBox(width: 8), Icon(Icons.arrow_forward_rounded, size: 18), ], ), ), ], ), ), ), ), ], ), ); } }
🧪 Prueba este componente

En main.dart, cambia temporalmente home: por const HomePages(). Corre la app: debes ver la imagen de fondo con el título "Café Místico" y el botón "EXPLORAR MENÚ" (aunque al presionarlo aún dé error, porque Menu no existe todavía — es normal en este punto).

PASO 13
Pantalla principal del menú (pages/menu.dart)
💻 Código
Esta pantalla integra todos los widgets construidos y consulta el backend con FutureBuilder. Primero, el estado:
dart🔒 import 'package:flutter/material.dart'; import 'package:ecommer_app/widgets/top_bar.dart'; import 'package:ecommer_app/widgets/search_bar_with_filter.dart'; import 'package:ecommer_app/widgets/category_selector.dart'; import 'package:ecommer_app/widgets/product_card.dart'; import 'package:ecommer_app/models/product.dart'; import 'package:ecommer_app/services/product_service.dart'; class Menu extends StatefulWidget { const Menu({super.key}); @override State<Menu> createState() => _MenuState(); } class _MenuState extends State<Menu> { int _selectedIndex = 0; late Future<List<Product>> _productsFuture; @override void initState() { super.initState(); _cargarProductos(); } Future<void> _cargarProductos() async { setState(() { _productsFuture = ProductService.getProductos(); }); }
Luego el build con el encabezado deslizable y el FutureBuilder que maneja los 4 estados posibles:
dart🔒 @override Widget build(BuildContext context) { return Scaffold( backgroundColor: const Color(0xFF181513), body: SafeArea( child: RefreshIndicator( color: const Color(0xFFC88D51), backgroundColor: const Color(0xFF1F1B18), onRefresh: _cargarProductos, child: CustomScrollView( physics: const AlwaysScrollableScrollPhysics(), slivers: [ SliverToBoxAdapter( child: Padding( padding: const EdgeInsets.all(20.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: const [ TopBar(), SizedBox(height: 24), SearchBarWithFilter(), SizedBox(height: 24), CategorySelector(), SizedBox(height: 12), ], ), ), ), FutureBuilder<List<Product>>( future: _productsFuture, builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { return const SliverToBoxAdapter( child: Padding( padding: EdgeInsets.symmetric(vertical: 40.0), child: Center(child: CircularProgressIndicator(color: Color(0xFFC88D51))), ), ); } if (snapshot.hasError) { return SliverToBoxAdapter( child: Padding( padding: const EdgeInsets.symmetric(vertical: 40.0), child: Center( child: Text( 'Error al obtener productos de la BD:\n${snapshot.error}', style: const TextStyle(color: Color(0xFFF5E6D3)), textAlign: TextAlign.center, ), ), ), ); } if (!snapshot.hasData || snapshot.data!.isEmpty) { return const SliverToBoxAdapter( child: Padding( padding: EdgeInsets.symmetric(vertical: 40.0), child: Center( child: Text('No hay productos registrados en la base de datos', style: TextStyle(color: Color(0xFFF5E6D3))), ), ), ); } final products = snapshot.data!; return SliverPadding( padding: const EdgeInsets.symmetric(horizontal: 20.0), sliver: SliverGrid( gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 2, mainAxisSpacing: 16.0, crossAxisSpacing: 16.0, childAspectRatio: 0.60, ), delegate: SliverChildBuilderDelegate( (context, index) { final product = products[index]; return ProductCard(product: product, onTap: () {}, onAddTap: () {}); }, childCount: products.length, ), ), ); }, ), const SliverToBoxAdapter(child: SizedBox(height: 24)), ], ), ), ),
Y para cerrar, la barra de navegación inferior:
dart🔒 bottomNavigationBar: BottomNavigationBar( currentIndex: _selectedIndex, onTap: (index) => setState(() => _selectedIndex = index), backgroundColor: const Color(0xFF1F1B18), type: BottomNavigationBarType.fixed, selectedItemColor: const Color(0xFFC88D51), unselectedItemColor: Colors.white.withValues(alpha: 0.35), showUnselectedLabels: false, items: const [ BottomNavigationBarItem(icon: Icon(Icons.home_filled), label: 'Inicio'), BottomNavigationBarItem(icon: Icon(Icons.favorite_rounded), label: 'Favoritos'), BottomNavigationBarItem(icon: Icon(Icons.shopping_bag_rounded), label: 'Carrito'), BottomNavigationBarItem(icon: Icon(Icons.person_rounded), label: 'Perfil'), ], ), ); } }
🧪 Prueba este componente

Con el backend corriendo, navega desde HomePages presionando "EXPLORAR MENÚ". Debes ver primero el círculo de carga y luego la grilla de 2 columnas con las tarjetas reales de la base de datos. Desliza hacia abajo para refrescar.

PASO 14
Conectar todo en main.dart
💻 Código
Cierra el proyecto de hoy dejando el home: definitivo:
dart🔒 import 'package:flutter/material.dart'; import 'package:ecommer_app/pages/home.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, home: const HomePages(), ); } }
🧪 Prueba final de la sesión

Haz flutter run desde cero: la app debe abrir directamente en "Café Místico", y al presionar "EXPLORAR MENÚ" debe llevarte al menú con el TopBar, el buscador, las categorías y la grilla de productos conectada a tu base de datos.

Proyecto base de Café Místico completado
Próxima sesión: carrito de compras, favoritos y perfil de usuario.
← Fase 3 · Componentes Ver checklist de progreso ✅