SENA - ADSO

Desarrollo Móvil:Flutter + OpenAPI

Guía completa de aprendizaje para construir aplicaciones móviles profesionales conFlutter 3.x,Dart 3.x,OpenAPI GeneratoryRiverpod, consumiendo microservicios REST con documentación OpenAPI.

40hDuración
MultiplataformaAndroid + iOS + Web
OpenAPIAPI First
MicroserviciosArquitectura
Dart 3Lenguaje
PostgreSQLBase de Datos
inventario_app.dart
@riverpod
class ProductoController extends _$ProductoController {
  @override
  Future<List<Producto>> build() async {
    final api = ref.read(productoApiProvider);
    return api.listarProductos();
  }

  Future<void> crear(ProductoCreate dto) async {
    final api = ref.read(productoApiProvider);
    await api.crearProducto(dto);
    await ref.invalidateSelf();
  }
}
01

Identificación de la Guía

Información oficial del programa de formación

Programa Análisis y Desarrollo de Software (ADSO)
Nivel Técnico / Tecnólogo
Competencia Desarrollar la solución de software de acuerdo con el diseño y metodologías de desarrollo establecidas
Resultado Construir aplicaciones móviles multiplataforma conectadas a microservicios REST usando Flutter y OpenAPI
Duración 40 horas (20 teoría + 20 práctica)
Tecnologías Flutter 3.xDart 3.xOpenAPIRiverpodDioGoRouterPostgreSQLDockerFastAPICoolify
Metodología Aprendizaje basado en proyectos - API First → Cliente OpenAPI → App Flutter

Quiz Rápido

Verifica tu comprensión de la guía

Pregunta1/5
⏱️ Tiempo:00:000aciertos
¿Cuál es el resultado de aprendizaje esperado?
02

Presentación

Introducción al desarrollo móvil multiplataforma con Flutter

¿Por qué Flutter?

Flutter es el framework de UI multiplataforma de Google que te permite construir aplicaciones nativas para Android, iOS, Web y escritorio desde un solo código base. Sus ventajas:

  • Hot Reload:Cambios reflejados en milisegundos
  • Rendimiento nativo:Compilado a código ARM nativo
  • Widgets personalizados:UI completamente personalizable
  • Una sola base de código:3 plataformas con el mismo código

¿Por qué OpenAPI?

OpenAPI es el estándar industrial para documentar APIs REST. Al integrarlo con Flutter:

  • API First:El contrato define la comunicación
  • Generación automática:Cliente Dart generado desde la spec
  • Tipado seguro:DTOs generados con tipos de Dart
  • Documentación viva:Swagger UI interactivo

El flujo API First consiste en: diseñar la API con OpenAPI → generar el cliente Dart → consumir desde Flutter.

¿Por qué Microservicios?

La arquitectura de microservicios permite escalar el desarrollo de forma independiente:

  • Desacoplamiento:Cada servicio es independiente
  • Escalabilidad:Escalar solo lo necesario
  • Tecnología heterogénea:Diferentes stacks por servicio
  • Despliegue independiente:Cada servicio se despliega solo

Flutter se convierte en el frontend universal que consume estos microservicios, proporcionando una experiencia unificada al usuario.

Arquitectura de la Solución

Flutter App

Frontend multiplataforma

Riverpod + Dio

OpenAPI Spec

Contrato API REST

openapi.yaml

Microservicio

Backend desacoplado

FastAPI + PostgreSQL

Objetivos de Aprendizaje

Dominar Flutter

Construir interfaces nativas con widgets, navegación y estado

Consumir APIs REST

Integrar servicios web con Dio y OpenAPI Generator

Implementar Riverpod

Manejar estado global con Riverpod de forma reactiva

Desplegar con Docker

Contenerizar y desplegar apps en entornos de producción

03

Reflexión Inicial

El problema del código espagueti en apps móviles y cómo Flutter lo soluciona

Caso de Estudio: Apps sin Arquitectura

Imagina que heredas una app móvil hecha con WebViews, sin separación de capas, sin manejo de estado y con llamadas HTTP dispersas por toda la UI. Al abrir el código encuentras algo así:

Código Problemático
class ProductosScreen extends StatefulWidget {
  @override
  State<ProductosScreen> createState() => _ProductosScreenState();
}

class _ProductosScreenState extends State<ProductosScreen> {
  List? productos;
  String? error;
  bool cargando = false;

  @override
  void initState() {
    super.initState();
    cargarProductos();
  }

  Future<void> cargarProductos() async {
    setState(() => cargando = true);
    try {
      final http.Client client = http.Client();
      final response = await client.get(
        Uri.parse('https://api.ejemplo.com/productos'),
      );
      if (response.statusCode == 200) {
        final List data = json.decode(response.body);
        setState(() {
          productos = data;
          cargando = false;
        });
      }
    } catch (e) {
      setState(() {
        error = e.toString();
        cargando = false;
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    if (cargando) return CircularProgressIndicator();
    if (error != null) return Text('Error: $error');
    return ListView.builder(
      itemCount: productos?.length ?? 0,
      itemBuilder: (ctx, i) => ListTile(
        title: Text(productos![i]['nombre'] ?? ''),
      ),
    );
  }
}

Problemas de Calidad

  • URLs hardcodeadas en la UI
  • Manejo de estado manual con setState
  • Tipado inseguro (List dinámica)
  • Sin manejo de errores consistente

Problemas de Mantenibilidad

  • Lógica de negocio mezclada con UI
  • Sin inyección de dependencias
  • Difícil de probar unitariamente
  • Código duplicado en cada pantalla

Problemas de Escalabilidad

  • Sin separación de capas (UI + datos + red)
  • Sin caché de respuestas
  • Sin interceptors para tokens
  • Sin tipado de respuestas API

Preguntas de Reflexión

1

¿Qué problemas ves en tener URLs de API hardcodeadas en los widgets?

2

Si necesitas agregar autenticación a todas las llamadas HTTP, ¿cuántos archivos tendrías que modificar?

3

¿Cómo harías para probar la lógica de carga de productos sin hacer llamadas reales a la API?

4

¿Qué pasa si el backend cambia el nombre de un campo? ¿Cómo detectas el error?

5

¿Cómo soluciona Riverpod + OpenAPI Generator todos estos problemas?

04

Stack Tecnológico

Herramientas y frameworks que usarás en este módulo

Flutter 3.x

Framework de UI multiplataforma de Google. Usa Dart como lenguaje y compila a código nativo ARM para Android e iOS, y a JavaScript/WebAssembly para la web.

  • Widgets Material Design y Cupertino
  • Hot Reload en tiempo real
  • Rendering propio (Skia/Impeller)
  • Renderización a 60/120 FPS

Dart 3.x

Lenguaje moderno, tipado y compilado. Soporta programación orientada a objetos, funcional y reactiva.

  • Null safety incorporado
  • Records y pattern matching
  • Async/await nativo
  • Compilación AOT y JIT

OpenAPI Generator

Herramienta que genera automáticamente clientes API, modelos DTO y documentación a partir de un archivo de especificación OpenAPI (YAML/JSON).

  • Genera modelos tipados en Dart
  • Cliente HTTP con Dio
  • Soporte para tokens JWT
  • Integración con build_runner

Riverpod

Manejo de estado reactivo para Flutter. Heredero de Provider, pero más seguro, testable y flexible.

  • Inyección de dependencias nativa
  • Providers auto-disposables
  • Facilita el testing con override
  • Sin dependencia de BuildContext

Dio

Cliente HTTP para Dart con interceptors, timeouts, cancelación y Transformers.

  • Interceptors para tokens y logging
  • Timeouts configurables
  • Cancelación de peticiones
  • Parseo automático de JSON

GoRouter

Enrutador declarativo para Flutter con soporte para rutas protegidas, redirecciones y deep linking.

  • Rutas tipadas y anidadas
  • Redirect condicional (auth)
  • Deep links y parámetros URL
  • Animaciones entre rutas
05

1. Instalación del Entorno Flutter

Prepara tu máquina para desarrollar con Flutter y Dart

01

Instalar Flutter SDK

Descarga e instala Flutter SDK en tu sistema operativo

Requisitos mínimos

Windows 10+, 8 GB RAM, 10 GB espacio libre, Git instalado.

Opción 1: Instalador oficial

Navegador
# 1. Descarga Flutter desde https://flutter.dev
# 2. Ejecuta el instalador para Windows
# 3. Verifica la instalación:
flutter doctor

Opción 2: Chocolatey (recomendado)

Terminal
choco install flutter
flutter doctor
02

Configurar Android Studio

Instala el emulador de Android y las herramientas de desarrollo

Pasos para Android Studio

Terminal
# Aceptar licencias de Android
flutter doctor --android-licenses

# Verificar que Android SDK está detectado
flutter doctor -v

Variables de entorno recomendadas

~\.zshrc / System PATH
# Agregar al PATH (Windows: variables de entorno del sistema)
export ANDROID_HOME=$HOME/AppData/Local/Android/Sdk
export PATH=$PATH:$ANDROID_HOME/platform-tools
export PATH=$PATH:$ANDROID_HOME/emulator
export PATH=$PATH:$ANDROID_HOME/cmdline-tools/latest/bin
03

VS Code y Extensiones

Configura tu editor para productividad máxima

Extensiones esenciales

  • Flutter — Soporte oficial de Dart y Flutter
  • Dart — Análisis de código, autocompletado
  • Error Lens — Errores inline en el editor
  • Pubspec Assist — Agregar paquetes fácilmente
  • Awesome Flutter Snippets — Atajos de código
04

Verificar con flutter doctor

Comando mágico que verifica todo tu entorno

Terminal
flutter doctor
# Deberías ver todas las casillas en verde ✓
# ✔ Flutter SDK
# ✔ Android Toolchain
# ✔ Chrome (para web)
# ✔ Android Studio
# ✔ VS Code

Solución de problemas comunes

Si ves cruces ✗, lee el mensaje: suele ser licencias de Android o falta de Chocolatey. Ejecutaflutter doctor --android-licenseso reinstala el SDK.

05

Crear primer proyecto

¡Tu primera app Flutter funcionando!

Terminal
flutter create mi_app
cd mi_app
flutter run
06

2. Fundamentos de Dart

Variables, tipos, funciones y async — la base de todo en Flutter

Variables y Tipos

Dart 3 tiene null safety, inferencia de tipos y tipado fuerte.

tipos.dart
// --- Tipos básicos con null safety ---
int edad = 21;
double precio = 19.99;
String nombre = 'Flutter';
bool activo = true;

// --- Nullable (?) vs non-nullable ---
String? apodo;       // Puede ser null
int cantidad = 0;    // Nunca será null

// --- Type inference: var, final, const ---
var mensaje = 'Hola Dart';       // String inferido
final url = 'api.google.com';     // Inmutable en runtime
const pi = 3.1416;                // Constante en compilación

// --- Records (Dart 3+) ---
(String, int) usuario = ('Ana', 25);
({String nombre, int edad}) persona = (
  nombre: 'Luis',
  edad: 30,
);

Funciones

Dart soporta funciones de primera clase, parámetros opcionales y expresiones lambda.

funciones.dart
// --- Función con parámetros posicionales ---
double sumar(double a, double b) => a + b;

// --- Parámetros nombrados (required / opcional) ---
void saludar({required String nombre, String? apellido}) {
  print('Hola, $nombre ${apellido ?? ''}');
}

// --- Función como parámetro (callback) ---
void procesar(List<int> items, void Function(int) cb) {
  for (final item in items) cb(item);
}

// --- Lambda / closure ---
final duplicar = (int x) => x * 2;

// --- Pattern matching (Dart 3+) ---
String describir(dynamic valor) => switch (valor) {
  int v      => 'Entero: $v',
  String s   => 'Texto: $s',
  _          => 'Desconocido',
};

Clases y Objetos

Programación orientada a objetos con constructores, herencia y mixins.

clases.dart
// --- Clase con constructor posicional ---
class Producto {
  final int id;
  final String nombre;
  final double precio;

  const Producto(this.id, this.nombre, this.precio);

  // Factory constructor desde Map
  factory Producto.fromJson(Map<String, dynamic> json) =>
    Producto(json['id'], json['nombre'], json['precio']);
}

// --- Herencia con super parámetros ---
class ProductoElectronicos extends Producto {
  final int garantiaMeses;

  ProductoElectronicos(
    super.id, super.nombre, super.precio, this.garantiaMeses);
}

Async / Await

Programación asíncrona con Futures, Streams y async/await.

async.dart
// --- Future con async/await ---
Future<String> obtenerToken() async {
  await Future.delayed(Duration(seconds: 1));
  return 'token_jwt_123';
}

// --- Llamada secuencial ---
Future<void> cargarDatos() async {
  final token = await obtenerToken();
  final datos = await httpGet('/productos', token);
  print(datos);
}

// --- Streams con async* / yield ---
Stream<int> contador() async* {
  for (int i = 0; i < 5; i++) {
    await Future.delayed(Duration(seconds: 1));
    yield i;
  }
}

// --- Manejo de errores con try / catch ---
Future<void> obtenerProductos() async {
  try {
    final res = await api.listar();
    print('Productos: $res');
  } on DioException catch (e) {
    print('Error HTTP: ${e.response?.statusCode}');
  } catch (e) {
    print('Error inesperado: $e');
  }
}

Ejercicio rápido

Crea una claseUsuarioconid,nombreyemail. Implementa unfactory fromJsony un métodotoJson. Luego crea una función async que simule obtener un usuario desde una API y lo imprima.

07

3. Widgets y UI

StatelessWidget, StatefulWidget, layouts y temas

StatelessWidget

Widget inmutable: su configuración no cambia después de construirse. Ideal para UI pura.

stateless.dart
class TarjetaProducto extends StatelessWidget {
  final Producto producto;

  const TarjetaProducto({super.key, required this.producto});

  @override
  Widget build(BuildContext context) {
    return Card(
      child: ListTile(
        leading: Icon(Icons.inventory),
        title: Text(producto.nombre),
        subtitle: Text('\$${producto.precio}'),
        trailing: Icon(Icons.arrow_forward_ios),
      ),
    );
  }
}

StatefulWidget

Widget mutable que puede cambiar su apariencia con setState.

stateful.dart
class ContadorWidget extends StatefulWidget {
  const ContadorWidget({super.key});

  @override
  State<ContadorWidget> createState() => _ContadorWidgetState();
}

class _ContadorWidgetState extends State<ContadorWidget> {
  int _contador = 0;

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Text('Valor: $_contador'),
        FilledButton(
          onPressed: () {
            setState(() => _contador++);
          },
          child: Text('Incrementar'),
        ),
      ],
    );
  }
}

Widgets de Layout Esenciales

Column y Row

Layouts verticales y horizontales. Los pilares de toda UI en Flutter.

column_row.dart
Column(
  crossAxisAlignment: CrossAxisAlignment.start,
  children: [
    Text('Productos'),
    SizedBox(height: 8),
    Row(
      mainAxisAlignment: MainAxisAlignment.spaceBetween,
      children: [
        Text('Nombre'),
        Text('Precio'),
      ],
    ),
  ],
)

GridView y ListView

Listas y cuadrículas para mostrar colecciones de datos.

listas.dart
// ListView básico
ListView.builder(
  itemCount: productos.length,
  itemBuilder: (ctx, i) => ListTile(
    title: Text(productos[i].nombre),
  ),
)

// GridView
GridView.builder(
  gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
    crossAxisCount: 2,
  ),
  itemBuilder: (ctx, i) => TarjetaProducto(productos[i]),
)
09

5. Consumo de APIs REST

Dio, JSON, manejo de errores e interceptors

Configuración de Dio

Dio es el cliente HTTP más popular en Flutter. Soporta interceptors, timeouts y cancelación.

api_client.dart
final apiClientProvider = Provider<Dio>((ref) {
  final dio = Dio(BaseOptions(
    baseUrl: 'http://localhost:8000/api',
    connectTimeout: Duration(seconds: 10),
    receiveTimeout: Duration(seconds: 10),
    headers: {'Content-Type': 'application/json'},
  ));

  // Interceptor para token JWT
  dio.interceptors.add(InterceptorsWrapper(
    onRequest: (options, handler) {
      final token = ref.read(authProvider).token;
      if (token != null) {
        options.headers['Authorization'] = 'Bearer $token';
      }
      handler.next(options);
    },
    onError: (error, handler) {
      if (error.response?.statusCode == 401) {
        ref.read(authProvider.notifier).logout();
      }
      handler.next(error);
    },
  ));

  return dio;
});

Operaciones CRUD

Implementa las operaciones básicas con métodos HTTP estándar.

producto_api.dart
class ProductoApi {
  final Dio _dio;

  ProductoApi(this._dio);

  Future<List<Producto>> listar() async {
    final res = await _dio.get('/productos');
    return (res.data as List)
        .map((j) => Producto.fromJson(j))
        .toList();
  }

  Future<Producto> obtener(int id) async {
    final res = await _dio.get('/productos/$id');
    return Producto.fromJson(res.data);
  }

  Future<Producto> crear(ProductoCreate dto) async {
    final res = await _dio.post('/productos', data: dto.toJson());
    return Producto.fromJson(res.data);
  }

  Future<Producto> actualizar(int id, ProductoUpdate dto) async {
    final res = await _dio.put('/productos/$id', data: dto.toJson());
    return Producto.fromJson(res.data);
  }

  Future<void> eliminar(int id) async {
    await _dio.delete('/productos/$id');
  }
}

Manejo de Errores

manejo_errores.dart
// Clase genérica para respuestas
sealed class ApiResult<T> {
  const ApiResult();
}

class Success<T> extends ApiResult<T> {
  final T data;
  const Success(this.data);
}

class Failure<T> extends ApiResult<T> {
  final String mensaje;
  final int? statusCode;
  const Failure(this.mensaje, [this.statusCode]);
}

// Uso con switch expression
switch (resultado) {
  case Success(data: var productos):
    mostrarLista(productos);
  case Failure(mensaje: var msg):
    mostrarError(msg);
}
10

6. OpenAPI Generator para Dart

Genera clientes tipados desde la especificación OpenAPI

¿Qué es OpenAPI Generator?

OpenAPI Generator toma un archivo de especificación (YAML/JSON) y genera automáticamente:

  • Modelos DTO (Data Transfer Objects)
  • Cliente API con métodos tipados
  • Configuración de autenticación
  • Manejo de errores consistente

Esto elimina errores de tipeo, campos faltantes y URLs mal escritas.

Generar el Cliente Dart

Desde la terminal, ejecuta OpenAPI Generator con el template Dart.

Terminal
# Usando openapi-generator-cli (Java)
openapi-generator generate \
  -i openapi.yaml \
  -g dart-dio \
  -o lib/api \
  --additional-properties=pubName=inventario_api

# O usando la imagen Docker
docker run --rm -v ${PWD}:/local openapitools/openapi-generator-cli \
  generate -i /local/openapi.yaml \
  -g dart-dio -o /local/lib/api

Ejemplo de openapi.yaml

Especificación mínima para un CRUD de productos

openapi.yaml
openapi: "3.0.3"
info:
  title: API de Inventario
  version: "1.0.0"
paths:
  /productos:
    get:
      operationId: listarProductos
      responses:
        "200":
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Producto"
    post:
      operationId: crearProducto
      requestBody:
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ProductoCreate"
components:
  schemas:
    Producto:
      type: object
      properties:
        id: {type: integer}
        nombre: {type: string}
        precio: {type: number}
    ProductoCreate:
      type: object
      properties:
        nombre: {type: string}
        precio: {type: number}

Usar el Cliente Generado

Código tipado, sin errores de runtime por campos mal escritos

usar_api.dart
import 'package:inventario_api/inventario_api.dart';

// Provider del API generada
final productoApiProvider = Provider<ProductoApi>((ref) {
  final dio = ref.watch(apiClientProvider);
  return ProductoApi(dio);
});

// Riverpod controller usando el API generada
@riverpod
class Productos extends _$Productos {
  @override
  Future<List<Producto>> build() async {
    final api = ref.read(productoApiProvider);
    return api.listarProductos();
  }

  Future<void> crear(ProductoCreate dto) async {
    await ref.read(productoApiProvider).crearProducto(dto);
    ref.invalidateSelf();
  }
}
11

7. Microservicios

Arquitectura, patrones de comunicación y despliegue

Arquitectura de Microservicios

Cada microservicio es una unidad independiente con su propia base de datos y lógica de negocio.

  • Servicio de Productos:CRUD de productos e inventario
  • Servicio de Usuarios:Autenticación y perfiles
  • Servicio de Pedidos:Órdenes y facturación
  • API Gateway:Punto de entrada único

Comunicación Síncrona (REST)

Los microservicios se comunican mediante HTTP/REST con documentación OpenAPI.

servicio_productos.py
from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI(title="Productos Service")

class Producto(BaseModel):
    id: int
    nombre: str
    precio: float

@app.get("/productos")
async def listar():
    return [Producto(id=1, nombre="Laptop", precio=999.99)]

@app.get("/productos/{producto_id}")
async def obtener(producto_id: int):
    return Producto(id=producto_id, nombre="Laptop", precio=999.99)

Flujo de Comunicación

Flutter App

Cliente multiplataforma

API Gateway

Enrutamiento + Auth

Microservicios

Productos / Usuarios / Pedidos

Cada microservicio tiene su propia DB PostgreSQL, su propio contrato OpenAPI y se despliega independientemente en Coolify.

Patrón API Gateway

El API Gateway es el punto de entrada único. Centraliza autenticación, rate limiting y enrutamiento. Flutter solo conoce la URL del gateway, no la de cada microservicio individual.

12

8. Tutorial Práctico: App Inventario con OpenAPI

Construye una app Flutter completa conectada a un microservicio FastAPI

Proyecto Flutter listo para ejecutar

Descarga el proyecto Flutter completo con: Riverpod (estado reactivo), GoRouter (navegación), Dio (cliente HTTP con interceptors JWT), OpenAPI spec y pantallas CRUD listas para conectar al microservicio FastAPI.

Solo necesitas: flutter pub get && flutter run

Descargar proyecto Flutter (59 KB)
01

Crear el proyecto Flutter

Inicializa el proyecto con las dependencias necesarias

Terminal
flutter create inventario_app
cd inventario_app
code .

Agregar dependencias en pubspec.yaml

pubspec.yaml
dependencies:
  flutter:
    sdk: flutter
  flutter_riverpod: ^2.5.0
  riverpod_annotation: ^2.3.0
  go_router: ^14.0.0
  dio: ^5.4.0
  json_annotation: ^4.9.0

dev_dependencies:
  build_runner: ^2.4.0
  riverpod_generator: ^2.4.0
  json_serializable: ^6.8.0
02

Configurar la API con OpenAPI Generator

Genera el cliente Dart desde la especificación OpenAPI

Ya tienes la API funcionando

Si completaste el módulo de FastAPI, ya tienes el microservicio de inventario funcionando enhttp://localhost:8000. La spec OpenAPI está disponible enhttp://localhost:8000/openapi.json.

Terminal
# Descargar la spec OpenAPI del microservicio
curl http://localhost:8000/openapi.json > openapi.json

# Generar el cliente Dart
openapi-generator generate \
  -i openapi.json \
  -g dart-dio \
  -o lib/api \
  --additional-properties=pubName=inventario_api

# Agregar al pubspec.yaml
dependencies:
  inventario_api:
    path: lib/api
03

Crear la pantalla de listado

Muestra los productos usando Riverpod + el API generado

productos_screen.dart
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:inventario_api/inventario_api.dart';

final productoApiProvider = Provider((ref) {
  final dio = Dio(BaseOptions(baseUrl: 'http://localhost:8000'));
  return ProductoApi(dio);
});

@riverpod
class ProductosController extends _$ProductosController {
  @override
  Future<List<Producto>> build() async {
    final api = ref.read(productoApiProvider);
    return api.listarProductos();
  }
}

class ProductosScreen extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final productos = ref.watch(productosControllerProvider);
    return Scaffold(
      appBar: AppBar(title: Text('Inventario')),
      body: productos.when(
        data: (items) => ListView.builder(
          itemCount: items.length,
          itemBuilder: (_, i) => ListTile(
            title: Text(items[i].nombre ?? ''),
            subtitle: Text('\$${items[i].precio}'),
          ),
        ),
        error: (e, _) => Center(child: Text('Error: $e')),
        loading: () => Center(child: CircularProgressIndicator()),
      ),
    );
  }
}
04

Probar la app

Ejecuta en emulador o dispositivo físico

Terminal
# Listar dispositivos disponibles
flutter devices

# Ejecutar en el emulador
flutter run

# Hot reload automático con 'r' en la terminal
13

Ejercicios de Transferencia

Ejercicios retadores para demostrar tu dominio de Flutter + OpenAPI

1
Intermedio

Búsqueda en Tiempo Real

Agrega un buscador que filtre productos mientras el usuario escribe, consumiendo el endpoint de búsqueda del microservicio.

Requisitos:

  • Campo de texto con debounce de 300ms
  • Llamada al endpointGET /productos?q=term
  • Mostrar resultados en un ListView
  • Indicador de carga durante la búsqueda
  • Manejar errores de red
  • Bonus:Filtros por categoría y rango de precios

Pista:UsaTimer? _debouncepara implementar el debounce. Cancela el timer anterior en cadaonChanged.

2
Avanzado

Autenticación JWT

Implementa login con JWT, almacenamiento seguro del token y renovación automática.

Requisitos:

  • Pantalla de login con usuario y contraseña
  • Llamada aPOST /auth/login
  • Almacenar token conflutter_secure_storage
  • Interceptor de Dio que agregue el token
  • Redirección automática al login si 401
  • Bonus:Refresh token automático

Pista:Usadio.interceptors.add(QueuedInterceptorsWrapper)y maneja el 401 enonError.

3
Avanzado

App Multi-servicio

Conecta tu app Flutter a dos microservicios diferentes (Productos y Pedidos) usando un API Gateway.

Requisitos:

  • Configurar dos APIs generadas con OpenAPI
  • Pantalla de productos con opción "Agregar al carrito"
  • Pantalla de carrito que consuma el servicio de pedidos
  • Estado global con Riverpod para el carrito
  • Confirmación de pedido con número de orden
  • Bonus:Historial de pedidos con scroll infinito

Pista:UsaGateway /api/productosyGateway /api/pedidos. Configura el baseUrl del Dio al gateway.

14

Evidencias de Aprendizaje

Demuestra que has alcanzado los resultados de aprendizaje

Evidencia de Conocimiento

Cuestionario escrito individual

  • Explicar la arquitectura de widgets en Flutter
  • Diferencia entre StatelessWidget y StatefulWidget
  • Manejo de estado con Riverpod
  • Rol de OpenAPI Generator en el flujo API First
  • Ventajas de usar Dio vs http.Client
  • Flujo completo: Widget → Provider → API → Microservicio
  • GoRouter y rutas protegidas
  • Arquitectura de microservicios vs monolito
Peso: 30%

Evidencia de Desempeño

Proyecto práctico individual

  • App Flutter funcional conectada a un microservicio
  • Cliente generado con OpenAPI Generator
  • Mínimo 3 pantallas con GoRouter
  • Manejo de estado con Riverpod
  • Autenticación JWT implementada
  • Operaciones CRUD completas
  • Manejo de errores y carga
Peso: 50%

Evidencia de Producto

Proyecto colaborativo grupal

  • Repositorio GitHub con README y documentación
  • Mínimo 2 microservicios desplegados en Coolify
  • API Gateway configurado
  • OpenAPI spec publicada y versionada
  • App Flutter publicada en TestFlight/APK
  • Demo funcional con datos reales
  • Presentación técnica del proyecto
Peso: 20%

Criterios de Evaluación

Cada evidencia se evalúa con:Bajo (1.0-2.9),Básico (3.0-3.9),Alto (4.0-4.5)ySuperior (4.6-5.0). La nota final es el promedio ponderado. Para aprobar necesitas mínimo 3.0 en cada evidencia.