# Flutter — Ejemplos Completos de Referencia

Implementaciones completas de widgets, state management, navegación, animaciones y testing.

---

## StatefulWidget Completo con Animación

```dart
class ContadorAnimado extends StatefulWidget {
  const ContadorAnimado({super.key, required this.inicial});
  final int inicial;

  @override
  State<ContadorAnimado> createState() => _ContadorAnimadoState();
}

class _ContadorAnimadoState extends State<ContadorAnimado>
    with SingleTickerProviderStateMixin {
  late int _count;
  late AnimationController _controller;

  @override
  void initState() {
    super.initState();
    _count = widget.inicial;
    _controller = AnimationController(
      vsync: this,
      duration: const Duration(milliseconds: 300),
    );
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  void _incrementar() {
    setState(() { _count++; });
    _controller.forward(from: 0);
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Text('$_count', style: const TextStyle(fontSize: 48)),
        FloatingActionButton(onPressed: _incrementar, child: const Icon(Icons.add)),
      ],
    );
  }
}
```

---

## HookWidget (flutter_hooks)

```dart
import 'package:flutter_hooks/flutter_hooks.dart';

class BusquedaWidget extends HookWidget {
  const BusquedaWidget({super.key});

  @override
  Widget build(BuildContext context) {
    final controller = useTextEditingController();
    final query = useListenableSelector(controller, () => controller.text);
    final debouncedQuery = useDebounced(query, const Duration(milliseconds: 300));

    useEffect(() {
      return null;
    }, [debouncedQuery]);

    return TextField(
      controller: controller,
      decoration: const InputDecoration(
        hintText: 'Buscar...',
        prefixIcon: Icon(Icons.search),
      ),
    );
  }
}
```

---

## BLoC — Implementación Completa

```dart
import 'package:flutter_bloc/flutter_bloc.dart';

// Eventos
sealed class AutenticacionEvento {}
final class LoginSolicitado extends AutenticacionEvento {
  const LoginSolicitado({required this.email, required this.password});
  final String email, password;
}
final class CerrarSesion extends AutenticacionEvento {}

// Estados
sealed class AutenticacionEstado {}
final class NoAutenticado extends AutenticacionEstado {}
final class Cargando extends AutenticacionEstado {}
final class Autenticado extends AutenticacionEstado {
  const Autenticado(this.usuario);
  final Usuario usuario;
}
final class ErrorAutenticacion extends AutenticacionEstado {
  const ErrorAutenticacion(this.mensaje);
  final String mensaje;
}

// BLoC
class AutenticacionBloc extends Bloc<AutenticacionEvento, AutenticacionEstado> {
  AutenticacionBloc(this._authRepo) : super(NoAutenticado()) {
    on<LoginSolicitado>(_onLoginSolicitado);
    on<CerrarSesion>((_, emit) => emit(NoAutenticado()));
  }

  final AuthRepository _authRepo;

  Future<void> _onLoginSolicitado(
    LoginSolicitado evento,
    Emitter<AutenticacionEstado> emit,
  ) async {
    emit(Cargando());
    try {
      final usuario = await _authRepo.login(evento.email, evento.password);
      emit(Autenticado(usuario));
    } catch (e) {
      emit(ErrorAutenticacion(e.toString()));
    }
  }
}
```

---

## Navegación con GoRouter

```dart
import 'package:go_router/go_router.dart';

final appRouter = GoRouter(
  initialLocation: '/',
  redirect: (context, state) {
    final autenticado = context.read<AuthBloc>().state is Autenticado;
    final estaEnLogin = state.matchedLocation == '/login';
    if (!autenticado && !estaEnLogin) return '/login';
    if (autenticado && estaEnLogin) return '/';
    return null;
  },
  routes: [
    GoRoute(
      path: '/',
      builder: (context, state) => const HomeScreen(),
      routes: [
        GoRoute(
          path: 'usuarios/:id',
          builder: (context, state) => DetalleUsuarioScreen(
            id: state.pathParameters['id']!,
          ),
        ),
      ],
    ),
    GoRoute(
      path: '/login',
      builder: (context, state) => const LoginScreen(),
    ),
    ShellRoute(
      builder: (context, state, child) => ScaffoldConNavBar(child: child),
      routes: [
        GoRoute(path: '/inicio', builder: (_, __) => const InicioTab()),
        GoRoute(path: '/perfil', builder: (_, __) => const PerfilTab()),
      ],
    ),
  ],
);
```

---

## Platform Channels

```dart
import 'package:flutter/services.dart';

class BiometricoChannel {
  static const _channel = MethodChannel('com.miapp/biometrico');

  static Future<bool> autenticar(String motivo) async {
    try {
      final resultado = await _channel.invokeMethod<bool>(
        'autenticar',
        {'motivo': motivo},
      );
      return resultado ?? false;
    } on PlatformException catch (e) {
      throw BiometricoException(e.message ?? 'Error biométrico');
    }
  }
}
```

```kotlin
// android/app/src/main/kotlin/.../MainActivity.kt
class MainActivity : FlutterActivity() {
  override fun configureFlutterEngine(engine: FlutterEngine) {
    super.configureFlutterEngine(engine)
    MethodChannel(engine.dartExecutor.binaryMessenger, "com.miapp/biometrico")
      .setMethodCallHandler { call, result ->
        when (call.method) {
          "autenticar" -> {
            val motivo = call.argument<String>("motivo") ?: "Confirma tu identidad"
            result.success(true)
          }
          else -> result.notImplemented()
        }
      }
  }
}
```

---

## Animaciones Explícitas

```dart
class _LogoAnimadoState extends State<LogoAnimado>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<double> _escala;
  late Animation<double> _rotacion;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      vsync: this,
      duration: const Duration(seconds: 2),
    )..repeat(reverse: true);

    _escala = Tween<double>(begin: 0.8, end: 1.2).animate(
      CurvedAnimation(parent: _controller, curve: Curves.elasticInOut),
    );
    _rotacion = Tween<double>(begin: 0, end: 0.1).animate(
      CurvedAnimation(parent: _controller, curve: Curves.easeInOut),
    );
  }

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: _controller,
      builder: (context, child) {
        return Transform.scale(
          scale: _escala.value,
          child: Transform.rotate(angle: _rotacion.value, child: child),
        );
      },
      child: const FlutterLogo(size: 100),
    );
  }
}
```

---

## Testing Completo

### Widget Tests

```dart
import 'package:flutter_test/flutter_test.dart';

void main() {
  group('TarjetaUsuario', () {
    final usuario = Usuario(id: '1', nombre: 'Ana López', email: 'ana@test.com');

    testWidgets('muestra nombre y email', (tester) async {
      await tester.pumpWidget(
        MaterialApp(home: TarjetaUsuario(usuario: usuario)),
      );
      expect(find.text('Ana López'), findsOneWidget);
      expect(find.text('ana@test.com'), findsOneWidget);
    });

    testWidgets('llama onTap al presionar', (tester) async {
      var llamado = false;
      await tester.pumpWidget(
        MaterialApp(
          home: TarjetaUsuario(usuario: usuario, onTap: () => llamado = true),
        ),
      );
      await tester.tap(find.byType(ListTile));
      expect(llamado, isTrue);
    });
  });
}
```

### Golden Tests

```dart
testWidgets('TarjetaUsuario golden', (tester) async {
  await tester.pumpWidget(
    MaterialApp(home: TarjetaUsuario(usuario: usuario)),
  );
  await expectLater(
    find.byType(TarjetaUsuario),
    matchesGoldenFile('goldens/tarjeta_usuario.png'),
  );
});

// Actualizar goldens:
// flutter test --update-goldens
```
