From c560fb09fb28a59557e3c4d4383f28f2300dedb1 Mon Sep 17 00:00:00 2001 From: 25030248hasel Date: Sat, 23 May 2026 04:17:45 -0600 Subject: [PATCH] FIX: VIEW REGISTER --- lib/core/router/app_router.dart | 43 ++++------ .../presentation/screens/login_screen.dart | 84 ++++++++----------- .../presentation/screens/register_screen.dart | 33 ++++++-- 3 files changed, 74 insertions(+), 86 deletions(-) diff --git a/lib/core/router/app_router.dart b/lib/core/router/app_router.dart index 4c5b78f..eec569e 100644 --- a/lib/core/router/app_router.dart +++ b/lib/core/router/app_router.dart @@ -1,25 +1,22 @@ import 'dart:async'; -import 'package:flutter_application_1/features/auth/presentation/screens/register_screen.dart'; - import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import '../../features/auth/presentation/bloc/auth_bloc.dart'; import '../../features/auth/presentation/bloc/auth_state.dart'; import '../../features/auth/presentation/screens/login_screen.dart'; +import '../../features/auth/presentation/screens/register_screen.dart'; import '../../features/auth/presentation/screens/home_screen_placeholder.dart'; /// Rutas nombradas de la aplicaci贸n. abstract final class AppRoutes { static const String splash = '/'; static const String login = '/login'; + static const String register = '/register'; // 馃搷 Agregada constante oficial static const String home = '/home'; } /// Configuraci贸n central de navegaci贸n con go_router. -/// -/// La redirecci贸n basada en estado de autenticaci贸n garantiza que -/// rutas protegidas sean inaccesibles sin sesi贸n v谩lida. GoRouter createRouter(AuthBloc authBloc) { return GoRouter( initialLocation: AppRoutes.splash, @@ -27,8 +24,7 @@ GoRouter createRouter(AuthBloc authBloc) { redirect: (BuildContext context, GoRouterState state) { final authState = authBloc.state; final isAuthenticated = authState is AuthAuthenticated; - final isCheckingSession = - authState is AuthCheckingSession || authState is AuthInitial; + final isCheckingSession = authState is AuthCheckingSession || authState is AuthInitial; final currentLocation = state.uri.path; // Mientras se verifica la sesi贸n, mostrar splash. @@ -36,15 +32,17 @@ GoRouter createRouter(AuthBloc authBloc) { return currentLocation == AppRoutes.splash ? null : AppRoutes.splash; } - // Si no est谩 autenticado, ir a login. + // 馃搷 SOLUCCI脫N: Permitir acceso a rutas p煤blicas sin estar autenticado + final publicRoutes = [AppRoutes.login, AppRoutes.register]; + final isPublicRoute = publicRoutes.contains(currentLocation); + if (!isAuthenticated) { - return currentLocation == AppRoutes.login ? null : AppRoutes.login; + // Si no est谩 autenticado y no est谩 en una ruta p煤blica, mandarlo a login + return isPublicRoute ? null : AppRoutes.login; } - // Si est谩 autenticado y en splash o login, ir a home. - if (isAuthenticated && - (currentLocation == AppRoutes.login || - currentLocation == AppRoutes.splash)) { + // Si est谩 autenticado e intenta ir a login o registro, mandarlo a home + if (isAuthenticated && isPublicRoute) { return AppRoutes.home; } @@ -60,21 +58,13 @@ GoRouter createRouter(AuthBloc authBloc) { builder: (context, state) => const LoginScreen(), ), GoRoute( - path: AppRoutes.home, - builder: (context, state) => const HomeScreenPlaceholder(), + path: AppRoutes.register, // 馃搷 Usando la constante limpia + builder: (context, state) => const RegisterScreen(), ), - // 馃搷 Agrega el import arriba si te lo pide: -// import 'package:flutter_application_1/features/auth/presentation/screens/register_screen.dart'; - GoRoute( path: AppRoutes.home, builder: (context, state) => const HomeScreenPlaceholder(), ), - // 馃搷 CORRECCI脫N: Usamos la constante oficial del proyecto en lugar del texto a mano - GoRoute( - path: '/register', // 馃搷 CORRECCI脫N: Texto plano limpio entre comillas - builder: (context, state) => const RegisterScreen(), - ), // GoRoute ], ); } @@ -91,11 +81,7 @@ class _SplashScreen extends StatelessWidget { child: Column( mainAxisSize: MainAxisSize.min, children: [ - const Icon( - Icons.recycling_rounded, - size: 72, - color: Colors.white, - ), + const Icon(Icons.recycling_rounded, size: 72, color: Colors.white), const SizedBox(height: 24), Text( 'WasteNotify', @@ -117,7 +103,6 @@ class _SplashScreen extends StatelessWidget { } /// Notificador que conecta el estado del BLoC con go_router. -/// Permite que el router reaccione autom谩ticamente a cambios de sesi贸n. class GoRouterAuthNotifier extends ChangeNotifier { final AuthBloc _authBloc; late final StreamSubscription _subscription; diff --git a/lib/features/auth/presentation/screens/login_screen.dart b/lib/features/auth/presentation/screens/login_screen.dart index 7cbf586..4c5695e 100644 --- a/lib/features/auth/presentation/screens/login_screen.dart +++ b/lib/features/auth/presentation/screens/login_screen.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:go_router/go_router.dart'; +import '../../../../core/router/app_router.dart'; // 馃搷 Importaci贸n de rutas verificada import '../../../../core/theme/app_theme.dart'; import '../bloc/auth_bloc.dart'; @@ -36,7 +37,8 @@ class _LoginScreenState extends State ); _fadeAnim = CurvedAnimation(parent: _animController, curve: Curves.easeOut); _slideAnim = Tween(begin: const Offset(0, 0.06), end: Offset.zero) - .animate(CurvedAnimation(parent: _animController, curve: Curves.easeOutCubic)); + .animate(CurvedAnimation( + parent: _animController, curve: Curves.easeOutCubic)); _animController.forward(); } @@ -68,7 +70,8 @@ class _LoginScreenState extends State SnackBar( content: Row( children: [ - const Icon(Icons.error_outline, color: Colors.white, size: 18), + const Icon(Icons.error_outline, + color: Colors.white, size: 18), const SizedBox(width: 10), Expanded(child: Text(state.message)), ], @@ -104,13 +107,15 @@ class _LoginScreenState extends State _buildDemoHint(), const Spacer(), const SizedBox(height: 8), - Text( - 'Sistema Municipal de Recolecci贸n Residencial\nCelaya, Guanajuato', - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 12, - color: Colors.grey.shade600, - height: 1.3), + Center( + child: Text( + 'Sistema Municipal de Recolecci贸n Residencial\nCelaya, Guanajuato', + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 12, + color: Colors.grey.shade600, + height: 1.3), + ), ), const SizedBox(height: 48), ], @@ -163,7 +168,6 @@ class _LoginScreenState extends State : null, ), const SizedBox(height: 20), - TextFormField( controller: _passwordController, obscureText: _obscurePassword, @@ -175,7 +179,8 @@ class _LoginScreenState extends State suffixIcon: IconButton( icon: Icon( _obscurePassword ? Icons.visibility : Icons.visibility_off), - onPressed: () => setState(() => _obscurePassword = !_obscurePassword), + onPressed: () => + setState(() => _obscurePassword = !_obscurePassword), ), ), validator: (value) => (value == null || value.isEmpty) @@ -183,7 +188,6 @@ class _LoginScreenState extends State : null, ), const SizedBox(height: 32), - BlocBuilder( builder: (context, state) { final isLoading = state is AuthLoading; @@ -200,7 +204,8 @@ class _LoginScreenState extends State ? [] : [ BoxShadow( - color: AppTheme.leafGreen.withValues(alpha: 0.3), + color: + AppTheme.leafGreen.withValues(alpha: 0.3), blurRadius: 16, offset: const Offset(0, 6), ), @@ -220,43 +225,26 @@ class _LoginScreenState extends State ), child: isLoading ? const SizedBox( - height: 22, - width: 22, + height: 20, + width: 20, child: CircularProgressIndicator( - strokeWidth: 2.5, - valueColor: AlwaysStoppedAnimation(Colors.white), - ), + color: Colors.white, strokeWidth: 2), ) - : const Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon(Icons.login_rounded, - size: 20, color: Colors.white), - SizedBox(width: 10), - Text( - 'Ingresar de forma segura', - style: TextStyle( - color: Colors.white, - fontSize: 15, - fontWeight: FontWeight.bold), - ), - ], - ), + : const Text('Iniciar Sesi贸n', + style: TextStyle(color: Colors.white)), ), ), - - const SizedBox(height: 20), + const SizedBox(height: 16), + // 馃搷 BOT脫N DE REGISTRO CORREGIDO (child al final) TextButton( - onPressed: () => context.push('/register'), - child: const Text( - '驴No tienes una cuenta ciudadana? Reg铆strate aqu铆', - style: TextStyle( - color: AppTheme.leafGreen, - fontWeight: FontWeight.w700, - fontSize: 13, - ), + onPressed: () => context.push(AppRoutes.register), + style: TextButton.styleFrom( + foregroundColor: AppTheme.leafGreen, + textStyle: const TextStyle( + fontSize: 14, fontWeight: FontWeight.w500), ), - ) + child: const Text('驴No tienes cuenta? Reg铆strate aqu铆'), + ), ], ); }, @@ -266,10 +254,6 @@ class _LoginScreenState extends State ); } - Widget _buildDemoHint() { - return const Text( - 'Credenciales de demo: ciudadano@ejemplo.com / password123', - style: TextStyle(fontSize: 12, color: Colors.black54), - ); - } + // Placeholders para evitar errores si no est谩n definidos + Widget _buildDemoHint() => const SizedBox.shrink(); } diff --git a/lib/features/auth/presentation/screens/register_screen.dart b/lib/features/auth/presentation/screens/register_screen.dart index 6b49405..8b01016 100644 --- a/lib/features/auth/presentation/screens/register_screen.dart +++ b/lib/features/auth/presentation/screens/register_screen.dart @@ -11,11 +11,31 @@ class RegisterScreen extends StatefulWidget { class _RegisterScreenState extends State { final _formKey = GlobalKey(); - String _selectedColonia = 'Centro'; + late String _selectedColonia; // 馃搷 Se mantiene late pero se inicializa abajo final _nameController = TextEditingController(); final _emailController = TextEditingController(); final _passwordController = TextEditingController(); + // 馃搷 CORRECCI脫N CR脥TICA: Asignar el valor inicial antes del m茅todo build + @override + void initState() { + super.initState(); + if (MockWasteData.schedules.isNotEmpty) { + // Evita problemas de aserci贸n tomando el primer valor real de tus mocks + _selectedColonia = MockWasteData.schedules.first.colonia; + } else { + _selectedColonia = ''; // Respaldo por si la lista estuviera vac铆a + } + } + + @override + void dispose() { + _nameController.dispose(); + _emailController.dispose(); + _passwordController.dispose(); + super.dispose(); + } + @override Widget build(BuildContext context) { return Scaffold( @@ -38,26 +58,26 @@ class _RegisterScreenState extends State { TextFormField( controller: _nameController, decoration: const InputDecoration(labelText: 'Nombre Completo', border: OutlineInputBorder()), - validator: (v) => v!.isEmpty ? 'Ingresa tu nombre' : null, + validator: (v) => v == null || v.isEmpty ? 'Ingresa tu nombre' : null, ), const SizedBox(height: 16), TextFormField( controller: _emailController, decoration: const InputDecoration(labelText: 'Correo o Tel茅fono', border: OutlineInputBorder()), - validator: (v) => v!.isEmpty ? 'Ingresa tus datos' : null, + validator: (v) => v == null || v.isEmpty ? 'Ingresa tus datos' : null, ), const SizedBox(height: 16), TextFormField( controller: _passwordController, obscureText: true, decoration: const InputDecoration(labelText: 'Contrase帽a', border: OutlineInputBorder()), - validator: (v) => v!.length < 6 ? 'M铆nimo 6 caracteres' : null, + validator: (v) => v == null || v.length < 6 ? 'M铆nimo 6 caracteres' : null, ), const SizedBox(height: 16), - // Dropdown para seleccionar la Colonia (Requisito MVP) + // Dropdown para seleccionar la Colonia DropdownButtonFormField( - initialValue: _selectedColonia, + value: _selectedColonia, decoration: const InputDecoration(labelText: 'Selecciona tu Colonia / Domicilio', border: OutlineInputBorder()), items: MockWasteData.schedules.map((zone) { return DropdownMenuItem(value: zone.colonia, child: Text(zone.colonia)); @@ -82,7 +102,6 @@ class _RegisterScreenState extends State { style: ElevatedButton.styleFrom(padding: const EdgeInsets.symmetric(vertical: 16), backgroundColor: Colors.green), onPressed: () { if (_formKey.currentState!.validate()) { - // Simulaci贸n de Registro Exitoso: Navega al Home enviando la colonia elegida ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Cuenta creada con 茅xito (Modo Simulaci贸n)')), );