Skip to content

Arquitectura Profesional

Documento de Arquitectura de Software — Feature-First Atomic Clean Architecture (Flutter 3.5+ / Dart 3.5+)

TeLlevo Pasajero — Documento de Arquitectura de Software

Section titled “TeLlevo Pasajero — Documento de Arquitectura de Software”

Plataforma: Flutter 3.5+ / Dart 3.5+ Arquitectura: Feature-First Atomic Clean Architecture Estado: Producción (v6.0.0+69) Códigobase: 494 archivos, 27,120 líneas de Dart, 13 features funcionales


  1. Resumen Ejecutivo
  2. Arquitectura General
  3. Estructura del Código
  4. Capas de la Arquitectura
  5. Patrón Use Case
  6. Widgets Atómicos
  7. State Management con Riverpod
  8. Service Discovery
  9. Navegación con GoRouter
  10. Refactorización Masiva
  11. Métricas de Calidad
  12. Stack Tecnológico
  13. Perfil del Desarrollador

TeLlevo Pasajero es una aplicación móvil de ridesharing que conecta suscriptores con colaborador. La aplicación soporta autenticación multi-tenant (individual y empresarial), pagos integrados con Khipu, mapas interactivos con Google Maps, seguimiento de viajes en tiempo real, y notificaciones push con Firebase Cloud Messaging.

Feature-First Atomic Clean Architecture: cada feature del negocio es autocontenida con sus propias capas de dominio, datos y presentación, construida mediante widgets atómicos de responsabilidad única.

IndicadorValor
Archivos totales494
Líneas de código27,120
Features funcionales13
Use cases (lógica de negocio pura)59
Widgets reutilizables134
Screens27
Providers Riverpod36
Datasources + Repositories69
Archivos < 30 líneas193 (39%)
Archivos < 100 líneas408 (82%)
Archivos generados (build_runner)37

La arquitectura del proyecto es una evolución pragmática de Clean Architecture adaptada a Flutter, donde cada feature del negocio contiene sus propias capas en lugar de tener capas globales.

graph TB
    subgraph "App Layer (lib/)"
        direction LR
        C[config/]<-->F[features/]
        F<-->E[exceptions/]
        F<-->U[utils/]
    end

    subgraph "Feature Layer (lib/features/)"
        direction TB
        F1[buscar/] --- F2[perfil/] --- F3[login/]
        F3 --- F4[viaje/] --- F5[shared/]
        F5 --- F6[pago/] --- F7[splash/]
        F7 --- F8[welcome/]
    end

    subgraph "Internal Feature Structure"
        direction LR
        D[datasources/]-->R[repositories/]
        R-->P[providers/]
        P-->SC[screens/]
        SC-->W[widgets/]
        UC[usecases/]-->SC
        M[models/]-->MA[mappers/]
        MA-->E2[entities/]
    end
sequenceDiagram
    participant Widget
    participant Provider
    participant UseCase
    participant Repository
    participant DataSource
    participant API

    Widget->>Provider: ref.watch(provider)
    Provider->>UseCase: validar/transformar
    UseCase-->>Provider: resultado
    Provider->>Repository: obtener datos
    Repository->>DataSource: llamada HTTP
    DataSource->>API: request (Dio)
    API-->>DataSource: response JSON
    DataSource-->>Repository: modelo parseado
    Repository-->>Provider: entidad de dominio
    Provider-->>Widget: nuevo estado (AsyncValue)

lib/
├── config/ # Configuración global
│ ├── constants/ # Constantes y entorno
│ ├── enums/ # Enums globales
│ ├── menu/ # Menús
│ ├── router/ # GoRouter modular
│ └── theme/ # Tema de la app
├── exceptions/ # Excepciones personalizadas
│ ├── general/
│ └── login/
├── features/ # 13 features funcionales
│ ├── buscar/ # Búsqueda de viajes (84 archivos)
│ ├── chat/ # Chat con colaborador
│ ├── enums/ # Enums de features
│ ├── error/ # Pantallas de error
│ ├── fcm/ # Firebase Cloud Messaging
│ ├── login/ # Autenticación (54 archivos)
│ ├── pago/ # Pagos Khipu (22 archivos)
│ ├── perfil/ # Perfil de usuario (91 archivos)
│ ├── resumen/ # Resumen de viaje (16 archivos)
│ ├── shared/ # Código compartido (61 archivos)
│ ├── splash/ # Splash screen (8 archivos)
│ ├── viaje/ # Viajes (62 archivos)
│ └── welcome/ # Onboarding (9 archivos)
├── rest/ # Documentación REST API
├── utils/ # Utilidades genéricas
└── main.dart # Entry point (99 líneas)

El proyecto usa dos patrones según la complejidad del feature:

lib/features/login/
├── data/ # Capa de datos
│ ├── datasources/ # 8 archivos (abstract + impl)
│ ├── entities/ # 1 entidad
│ ├── functions/ # 1 función compartida
│ ├── mappers/ # 2 mappers
│ └── repositories/ # 8 archivos (abstract + impl)
├── presentation/ # Capa de presentación
│ ├── providers/ # 11 providers (empresa/, login_respuesta)
│ ├── screens/ # 6 screens
│ └── widgets/ # 15 widgets atómicos
└── usecases/ # 7 use cases
lib/features/welcome/
├── screens/ # 1 screen
├── usecases/ # 3 use cases
└── widgets/ # 5 widgets atómicos

El módulo shared/ contiene todo el código transversal:

  • 28 widgets reutilizables (AppBar genérico, avatar, bottom nav, tarjetas, modales)
  • 13 use cases compartidos (routing, navegación, validaciones cross-feature)
  • 6 providers globales (usuario, estado de aplicación, mapa controller, health status)
  • 4 servicios de discovery (config, interceptor, service health monitor)

graph LR
    subgraph "Dominio (Domain)"
        E[Entities]
        UC[Use Cases <br/>59 archivos]
    end
    subgraph "Datos (Data)"
        D[Datasources <br/>35 archivos]
        M[Models <br/>15 archivos]
        MA[Mappers <br/>13 archivos]
        R[Repositories <br/>34 archivos]
    end
    subgraph "Presentación (UI)"
        P[Providers <br/>36 archivos]
        SC[Screens <br/>27 archivos]
        W[Widgets <br/>134 archivos]
    end

    E --> MA
    MA --> M
    D --> R
    R --> P
    UC --> P
    P --> SC
    SC --> W

Contiene las entidades del negocio y los use cases con lógica pura.

Entidades (22 archivos): Representan objetos de negocio sin dependencias de frameworks.

Coordenada, Prediction, TarifaCO2, Trayectoria, ChatMessage,
PerfilPasajero, PreferenciaViajeRequest, Tarjeta, PagoKhipu,
Viaje, ViajeActivo, Usuario, UsuarioEmpresa, DeviceToken, ...

Use Cases (59 archivos): Clases con un único método execute(), sin dependencias externas. Ver sección 5.

Implementa la comunicación con APIs REST y el mapeo de datos.

Datasources (35 archivos, 18 pares abstract + impl): Definen contratos de comunicación HTTP y sus implementaciones concretas con Dio.

login_datasource.dart (abstract)
login_datasource_impl.dart (concreto, Dio)
coordenada_datasource.dart (abstract)
coordenada_datasource_impl.dart (concreto)

Models (15 archivos): Representaciones JSON de las respuestas de API.

Mappers (13 archivos): Transforman Models → Entities y viceversa.

Repositories (34 archivos, 17 pares abstract + impl): Orquestan datasources y aplican lógica de negocio antes de entregar datos a los providers.

Construye la interfaz de usuario siguiendo el patrón de widgets atómicos.

Providers (36 archivos): Estado global con Riverpod (ver sección 7).

Screens (27 archivos, <80 líneas): Orquestan widgets y providers. No contienen lógica de negocio ni UI detallada.

Widgets (134 archivos, <100 líneas): Componentes de UI con una única responsabilidad.


Los Use Cases encapsulan lógica de negocio pura (validaciones, formateo, cálculos) fuera de la UI para maximizar la testabilidad y reutilización.

lib/features/{feature}/usecases/
├── index.dart # Barrel export
├── validar_email_usecase.dart
├── formatear_precio_usecase.dart
└── ...
flowchart LR
    A[UI Event] --> B[Provider/Controller]
    B --> C{Use Case}
    C -->|Validación| D[Resultado: ok/error]
    C -->|Formateo| E[Resultado: string formateado]
    C -->|Cálculo| F[Resultado: valor calculado]
    D --> B
    E --> B
    F --> B
    B --> G[Nuevo estado]
    G --> H[UI Rebuild]
/// Validates complete email format
class ValidarEmailCompletoUseCase {
const ValidarEmailCompletoUseCase();
/// Returns error message or null if valid
String? execute(String email) {
if (email.isEmpty) {
return 'El correo es un campo obligatorio';
}
if (!Utils.isValidEmail(email)) {
return 'Debe ser un correo válido';
}
return null;
}
}

Características:

  • 100% pureza funcional (sin efectos secundarios)
  • Sin dependencias de Flutter ni Riverpod
  • Testable de forma aislada
  • 20-60 líneas por use case
FeatureUse CasesPropósito
buscar10Lugares, distancias, tiempos, coordenadas
login7Validación de email, construcción de email, PIN
perfil12Validaciones de formulario (nombre, email, celular, dirección), formato
viaje8Formateo CO2, distancia, direcciones, tabs
resumen7Precios, bounds, polylines, coordenadas
pago5Tipo de pago, mensajes de error, navegación post-pago
shared13Routing, WhatsApp, logging, aplicaciones externas
splash1Inicialización de la app
welcome3Validación de token, preferencias de viaje
Total59

El principio fundamental de la UI: un archivo, una responsabilidad, máximo 100 líneas.

graph TB
    subgraph "Screen (orquestador)"
        S[EditarPerfilScreen<br/>64 líneas]
    end
    subgraph "Widgets Atómicos"
        W1[AppBarGenerico]
        W2[ProfileForm<br/>221 líneas]
        W3[ProfileLoadingView]
        W4[ProfileErrorView]
    end
    subgraph "Sub-Widgets"
        W5[EmailField<br/>42 líneas]
        W6[NombreField<br/>43 líneas]
        W7[CelularField<br/>49 líneas]
        W8[Avatar]
    end

    S --> W1
    S --> W2
    S --> W3
    S --> W4
    W2 --> W5
    W2 --> W6
    W2 --> W7
    W2 --> W8

Una Screen nunca contiene UI detallada. Solo orquesta:

class EditarPerfilScreen extends ConsumerWidget {
const EditarPerfilScreen({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final usuario = ref.watch(usuarioProvider);
final perfil = ref.watch(perfilPasajeroProvider(usuario.usuarioId));
final estado = ref.watch(estadoAplicacionProvider);
return Scaffold(
appBar: AppBarGenerico(title: 'Editar Perfil', ...),
body: SafeArea(
child: perfil.when(
loading: () => const ProfileLoadingView(),
error: (error, _) => ProfileErrorView(message: error.toString()),
data: (perfilData) => ProfileForm(
correoController: TextEditingController(text: perfilData.email),
nombreController: TextEditingController(text: perfilData.nombre),
...
),
),
),
);
}
}
class ProfileHeader extends StatelessWidget {
final String urlIcono;
final String nombre;
final int cantidadEstrellas;
const ProfileHeader({
super.key,
required this.urlIcono,
required this.nombre,
required this.cantidadEstrellas,
});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(left: 12, right: 12),
child: Row(
children: [
Avatar(urlIconoConductor: urlIcono, size: 50),
const SizedBox(width: 15),
Flexible(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(nombre, style: ...),
_buildStarRating(),
Text('Persona', style: ...),
],
),
),
],
),
);
}
}

85 líneas — Una responsabilidad: mostrar el encabezado del perfil con avatar, nombre y estrellas.

pie title "Distribución de Archivos Dart (excluyendo generados)"
    "≤ 30 líneas" : 193
    "31-50 líneas" : 111
    "51-80 líneas" : 71
    "81-100 líneas" : 33
    "> 100 líneas" : 50

El 82% de los archivos tiene ≤ 100 líneas. El 39% tiene ≤ 30 líneas.


Riverpod con generación de código (riverpod_annotation + build_runner) es el motor de estado de la aplicación.

TipoUsoEjemplo
@riverpodDatos asíncronos (API calls)perfilPasajeroProvider
@Riverpod(keepAlive: true)Estado persistente globalUsuarioNotifier, EstadoAplicacion
Providers manualesLógica de controller complejakhipuPaymentController, mapaControllerProvider
@riverpod
Future<PerfilPasajero> perfilPasajero(Ref ref, int usuarioId) async {
final repository = PerfilPasajeroRepositoryImpl(
dataSource: PerfilPasajeroDatasourceImpl(),
);
try {
return await repository.obtenerPerfilPasajero(usuarioId);
} on DioException catch (e) {
final responseApi = ResponseApi.fromDioException(e);
throw InfrastructureException(
"Error controlado de la API",
responseApi, e,
);
}
}
@Riverpod(keepAlive: true)
class UsuarioNotifier extends _$UsuarioNotifier {
@override
UsuarioEmpresa build() {
return UsuarioEmpresa(usuarioId: 0, email: "");
}
void setUsuario(UsuarioEmpresa usuario) {
state = usuario;
}
}
stateDiagram-v2
    [*] --> Activo: Widget se monta
    Activo --> Disposed: Widget se desmonta (auto-dispose)
    Activo --> Activo: ref.watch(otroProvider)
    
    note right of Activo
        @riverpod: auto-dispose por defecto
        @Riverpod(keepAlive: true): persiste siempre
    end note

37 archivos .g.dart generados por build_runner a partir de las anotaciones @riverpod. Esto elimina el boilerplate manual de creación de providers.


La aplicación implementa un sistema de Service Discovery dinámico que obtiene las URLs de los microservicios backend desde un Cloudflare Worker, eliminando URLs hardcodeadas.

flowchart TB
    subgraph "App Startup"
        A[App Init] --> B[DiscoveryService.init]
    end

    subgraph "Cloudflare Worker"
        C[Discovery Worker<br/>api.tellevoapp.cl/discovery]
        D[(Config<br/>dinámica)]
        C --> D
    end

    subgraph "AppConfig"
        E[core: api.tellevoapp.com:7080]
        F[pagos: pagos.tellevoapp.com:7081]
        G[login: login.tellevoapp.com:7083]
        H[htmls: htmls.tellevoapp.com:7082]
    end

    B -->|GET /discovery| C
    C -->|JSON response| B
    B -->|parse| E
    B -->|parse| F
    B -->|parse| G
    B -->|parse| H

    subgraph "Runtime"
        I[Dio Request: /pagos/khipu]
        J[DiscoveryInterceptor]
        K[URL final: pagos.tellevoapp.com:7081/khipu]
        I --> J
        J --> K
    end

Un interceptor de Dio que reescribe las URLs en tiempo real según la configuración obtenida:

class DiscoveryInterceptor extends Interceptor {
@override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
final config = DiscoveryService.config;
if (options.path.startsWith('/pagos')) {
options.baseUrl = config.pagos;
} else if (options.path.startsWith('/login')) {
options.baseUrl = config.login;
} else if (options.path.startsWith('/htmls')) {
options.baseUrl = config.htmls;
} else {
options.baseUrl = config.core;
}
return handler.next(options);
}
}
sequenceDiagram
    participant App
    participant DiscoveryService
    participant Worker as Cloudflare Worker
    participant Health as ServiceHealthMonitor
    participant Router as GoRouter

    App->>DiscoveryService: init()
    DiscoveryService->>Worker: GET /discovery
    Worker-->>DiscoveryService: {core, pagos, login, htmls}
    DiscoveryService-->>App: AppConfig válida
    App->>Health: checkAllServices()
    Health->>core: healthcheck
    Health->>pagos: healthcheck
    Health->>login: healthcheck
    Health-->>App: All services OK
    App->>Router: navegar a /splash
    Router->>RouterRedirectHandler: ¿ruta protegida?
    Router-->>App: redirigir a splash si no hay health
EscenarioComportamiento
Worker timeoutSplash con botón de reintento
Config inválidaDiscoveryException(isRetryable: true)
Health check fallaPantalla ServiceHealthErrorScreen con servicios caídos
Sin conexiónMensaje: “Verifica tu conexión”

La navegación usa GoRouter con un diseño modular donde cada feature define sus propias rutas.

lib/config/router/
├── app_router.dart # Router principal (61 líneas)
├── router_redirect_handler.dart # Protección de rutas (48 líneas)
├── routes/
│ ├── app_routes.dart # Constantes de ruta
│ ├── root_routes.dart # /splash, /error
│ ├── main_routes.dart # /welcome, /buscar, /viaje
│ ├── login_routes.dart # /seleccion_empresa, /empresa/*
│ ├── payment_routes.dart # /khipu, /tarjetaPago
│ ├── profile_routes.dart # /perfil, /editar_perfil
│ └── info_routes.dart # /acerca_de, /terminos
└── index.dart # Barrel file
graph TB
    subgraph "Sin autenticación"
        S[/splash]
        E[/error]
    end

    subgraph "Shell Route (Bottom Nav)"
        W[/welcome]
        B[/buscar]
        V[/viaje]
        P[/perfil]
    end

    subgraph "Login Flow"
        LE[/seleccion_empresa]
        LP[/empresa/:dominio/pin]
        LU[/empresaUnirme]
        LT[/terminos]
    end

    subgraph "Sub-rutas"
        EP[/editar_perfil]
        PV[/preferencia_viaje]
        KH[/khipu]
        TP[/tarjetaPago]
        MP[/medioPago]
        AD[/acerca_de]
    end

    S -->|Discovery OK| W
    S -->|Discovery fail| E
    E -->|Retry| S
    W -->|Login| LE
    LE --> LP
    LE --> LU
    LP --> B
    B --> KH
    KH --> V
    V --> P
    P --> EP
    P --> PV
    P --> AD
    P --> TP
    P --> MP

El RouterRedirectHandler verifica que el servicio de discovery esté operativo antes de permitir el acceso a rutas protegidas:

final handler = RouterRedirectHandler(
VerificarRutaProtegidaUseCase(protectedRoutes),
);
return handler.handleRedirect;

17 rutas definidas, de las cuales las rutas principales son protegidas (requieren health check exitoso).


En abril de 2025 se realizó una refactorización completa de 18 archivos críticos que transformó monolitos de hasta 622 líneas en 220+ módulos atómicos.

Archivo OriginalLíneasArchivos NuevosLíneas PromedioReducción
componentes.dart458596-79%
preferencia_viaje.dart6221252-92%
viaje.dart5981443-93%
shell_router.dart4261431-93%
formulario.dart4211283-80%
editar_perfil.dart3971428-93%
mapa.dart4071429-93%
khipu.dart271834-88%
welcome.dart226736-84%
splash_screen.dart280734-88%
flowchart LR
    subgraph "Paso 1: Extraer Use Cases"
        A[Monolito: 397 líneas] --> B[usecases/<br/>5 archivos<br/>85 líneas total]
    end
    subgraph "Paso 2: Extraer Widgets"
        A --> C[widgets/<br/>10 archivos<br/>340 líneas total]
    end
    subgraph "Paso 3: Refactorizar Screen"
        A --> D[screen.dart<br/>63 líneas<br/>solo orquestación]
    end
    subgraph "Paso 4: Wrapper Legacy"
        A --> E[wrapper.dart<br/>24 líneas<br/>@deprecated + export]
    end

    B --> D
    C --> D

Todos los archivos originales se mantienen como wrappers deprecados para no romper imports existentes:

/// DEPRECATED: Use 'editar_perfil_screen.dart' instead
@deprecated
export 'editar_perfil_screen.dart' show EditarPerfilScreen;
@deprecated
typedef EditarPerfil = EditarPerfilScreen;

Esto permite una migración gradual donde el código legacy sigue funcionando mientras se actualizan los imports.


FeatureArchivosLíneas% del Códigobase
perfil915,58620.6%
buscar843,54413.1%
viaje623,82714.1%
shared614,58516.9%
login543,81814.1%
pago221,6115.9%
resumen169843.6%
config238483.1%
splash84311.6%
welcome93561.3%
error22370.9%
utils76662.5%
exceptions71280.5%
otros484991.8%
Total49427,120100%
graph LR
    subgraph "Componentes del Códigobase"
        UC[Use Cases: 59]
        SC[Screens: 27]
        WI[Widgets: 134]
        PR[Providers: 36]
        DS[Datasources: 35]
        RE[Repositories: 34]
        EN[Entities: 22]
        MO[Models: 15]
        MA[Mappers: 13]
        BA[Barrel Files: 28]
        GE[Generated: 37]
    end
RangoArchivosPorcentajeAcumulado
≤ 30 líneas19339.1%39.1%
31-50 líneas11122.5%61.5%
51-80 líneas7114.4%75.9%
81-100 líneas336.7%82.6%
> 100 líneas5010.1%92.7%
Generados (.g.dart)367.3%100.0%
xychart-beta
    title "Distribución de Tamaño de Archivos Dart"
    x-axis ["≤ 30", "31-50", "51-80", "81-100", "> 100", ".g.dart"]
    y-axis "Archivos" 0 --> 200
    bar [193, 111, 71, 33, 50, 36]
  1. Responsabilidad Única: El 82% de los archivos tiene ≤ 100 líneas, demostrando adherencia estricta al principio de Single Responsibility.

  2. Código Atómico: El 39% de los archivos tiene ≤ 30 líneas — componentes verdaderamente atómicos con una sola función.

  3. Balance Feature/Shared: Las features de negocio representan el 90% del código, mientras que shared/ aporta el 17% como capa transversal reutilizable.

  4. Cobertura de Use Cases: 59 use cases extraen toda la lógica de negocio fuera de la UI, maximizando la testabilidad.

  5. Generación de Código: 37 archivos generados (7.3%) eliminan boilerplate y aseguran consistencia en providers.


TecnologíaVersiónPropósito
Flutter^3.5.0Framework cross-platform
Dart^3.5.0Lenguaje de programación
Android minSdk21Versión mínima soportada
Android targetSdk36Versión target
PaqueteVersiónPropósito
flutter_riverpod^3.0.3State management reactivo
hooks_riverpod^3.0.3State management con hooks
riverpod_annotation^4.0.0Anotaciones para code generation
riverpod_generator^4.0.0+1Generación de providers
provider^6.0.5Legacy (en migración)
PaqueteVersiónPropósito
dio^5.7.0HTTP client con interceptors
http^1.0.0HTTP client estándar
go_router^17.0.1Navegación declarativa
PaqueteVersiónPropósito
google_maps_flutter^2.9.0Mapas interactivos
geolocator^14.0.2Servicios de ubicación GPS
geocoding^4.0.0Geocoding y reverse geocoding
flutter_polyline_points^1.0.0Políneas de rutas
PaqueteVersiónPropósito
flutter_khipu^1.5.3SDK de pagos Khipu (Chile)
PaqueteVersiónPropósito
firebase_core^4.4.0Firebase core
firebase_messaging^16.1.1Push notifications FCM
flutter_local_notifications^21.0.0Notificaciones locales
PaqueteVersiónPropósito
google_fonts^6.3.3Tipografía
font_awesome_flutter^11.0.0Iconos
flutter_svg^2.0.9SVG rendering
auto_size_text^3.0.0Texto responsive
flutter_html^3.0.0-beta.2HTML rendering
PaqueteVersiónPropósito
build_runner^2.4.12Code generation
flutter_lints^6.0.0Linting
riverpod_lint^3.0.3Riverpod-specific linting
custom_lint^0.8.1Custom lint rules

Gonzalo Eduardo Oviedo Lambert — Arquitecto de Software Flutter/Dart

HabilidadEvidencia
Clean ArchitectureImplementación completa de Feature-First Atomic Clean Architecture con 4 capas por feature
State Management AvanzadoRiverpod con generación de código, manejo de ciclo de vida, keepAlive estratégico
Refactorización Sistemática18 monolitos → 220+ módulos atómicos con backward compatibility
Patrones de DiseñoRepository, Interceptor (Dio), Adapter (Mappers), Composition (Widgets), Singleton (DiscoveryService)
Arquitectura de RedService Discovery dinámico con Cloudflare Workers, interceptors de URL
Calidad de Código82% de archivos ≤ 100 líneas, 0 errores de análisis estático
Navegación ComplejaGoRouter modular con ShellRoute, redirect handler, rutas protegidas
Documentación TécnicaDocs por feature, ADRs, diagramas de flujo, guías de troubleshooting
Manejo de ErroresCapa de excepciones personalizadas, manejo de DioExceptions, estados de error en UI
Testing y CIflutter analyze, flutter test, build_runner, análisis estático integrado
Integración Multi-ServicioAPIs REST, Firebase, Khipu, Google Maps, FCM push notifications
Multi-tenenciaLogin individual y empresarial con verificación por dominio y PIN
Código limpio > Código que funciona
Arquitectura > Framework
Testing > Documentación
Reutilización > Copiar y pegar
Mantenibilidad > Velocidad inicial

Documento generado a partir del análisis del códigobase de TeLlevo Pasajero v6.0.0+69 494 archivos Dart, 27,120 líneas, 13 features, 59 use cases, 134 widgets atómicos Refactorización Abril 2025: 18 monolitos → 220+ módulos