1 00:00:02,480 --> 00:00:06,839 Hola, mi nombre es Juan Herrera y soy el desarrollador de MyPilot. 2 00:00:08,179 --> 00:00:12,560 MyPilot es una aplicación hecha para Android que es mi trabajo de fin de grado. 3 00:00:13,720 --> 00:00:18,739 La idea es bastante simple. Hay situaciones en las que uno tiene un coche, 4 00:00:19,359 --> 00:00:24,739 pero en ese momento no quiere o no puede manejarlo, ya sea porque ha bebido de más, 5 00:00:24,980 --> 00:00:27,699 porque tiene una incapacidad física temporal, por lo que sea. 6 00:00:28,559 --> 00:00:30,179 MyPilot es donde entra en ese momento. 7 00:00:30,179 --> 00:00:37,060 La aplicación te permite pedir un conductor que se acerca a donde estás y te lleva a ti en tu vehículo hacia donde quieras 8 00:00:37,060 --> 00:00:40,340 Es parecido a Uber o a Cabify, pero al revés 9 00:00:40,340 --> 00:00:45,100 En esas aplicaciones el conductor llega en su propio vehículo, te recoge y te lleva 10 00:00:45,100 --> 00:00:52,640 Aquí el conductor llegaría o en transporte público, o en bicicleta, o en algún otro medio de transporte o a pie 11 00:00:52,640 --> 00:00:56,000 Y utiliza tu vehículo para llevarte a sitios 12 00:00:56,000 --> 00:00:59,299 El sistema está compuesto por tres partes 13 00:00:59,880 --> 00:01:02,740 El backend, que es el cerebro de todo, que es un API restricado. 14 00:01:03,240 --> 00:01:05,659 Y las otras dos aplicaciones, que son las que van en el teléfono. 15 00:01:06,299 --> 00:01:09,840 MyPilot, que va en el teléfono del viajero o el consumidor final. 16 00:01:10,379 --> 00:01:13,140 Y MyPilotDriver, que es la app que va a manejar el conductor. 17 00:01:14,140 --> 00:01:17,420 Las tres están conectadas entre sí y funcionan en tiempo real. 18 00:01:18,480 --> 00:01:20,920 La arquitectura del sistema es de cliente-servidor. 19 00:01:21,200 --> 00:01:28,939 Las dos aplicaciones en Kotlin se comunican con la API a través de dos canales principales de comunicación. 20 00:01:29,299 --> 00:01:37,959 que son REST, para llamadas que no tienen necesidad de que sean inmediatas, ni que sean de respuesta bidireccional, 21 00:01:38,859 --> 00:01:45,079 como hacer un rating, por ejemplo, que después de terminar un viaje se le pone una calificación a la otra persona, 22 00:01:45,079 --> 00:01:51,420 y a través de WebSockets para las actividades o eventos que tienen que aparecer en tiempo real. 23 00:01:51,420 --> 00:01:59,500 El uso de WebSockets y protocolos Storm permiten que la aplicación sea mucho más eficiente 24 00:01:59,500 --> 00:02:06,540 a la hora de mantener un contacto en vivo con una API, porque en vez de estar llamando 25 00:02:06,540 --> 00:02:12,300 constantemente o periódicamente a la API diciendo ocurrió algo, ocurrió algo, ocurrió 26 00:02:12,300 --> 00:02:19,979 algo, la API no tiene ningún evento nuevo, simplemente espera a que la API cuando ocurra 27 00:02:19,979 --> 00:02:24,780 algo realmente, le notifique. Eso lo hace mucho más eficiente en tiempo de ejecución 28 00:02:24,780 --> 00:02:27,280 y en batería, en uso de batería. 29 00:02:28,419 --> 00:02:34,520 El objetivo del proyecto era construir todo desde cero. El backend, la frontend, las aplicaciones 30 00:02:34,520 --> 00:02:42,199 en Android, la comunicación en tiempo real y el funcionamiento entre sí, especialmente 31 00:02:42,199 --> 00:02:43,780 conectado. Vamos a verlo. 32 00:02:44,840 --> 00:02:49,020 Para el backend utilicé Java con Springwood, que es el framework estándar para APIs REST 33 00:02:49,020 --> 00:02:56,659 en Java. La base de datos es MySQL gestionada con JPA e Hibernate, que me permiten saltarme 34 00:02:56,659 --> 00:03:04,060 a escribir el SQL a mano y simplemente trabajar directamente con los objetos Java. Esto lo 35 00:03:04,060 --> 00:03:08,960 podemos ver en los entities, más que nada, con estas anotaciones, las importaciones del 36 00:03:08,960 --> 00:03:15,840 on-book para además saltarme a hacer los gets y sets, y a las transformaciones de algunos 37 00:03:15,840 --> 00:03:22,259 elementos de la tabla de bases de datos a DTOS, porque no utilizaba todos los elementos de la tabla. 38 00:03:25,379 --> 00:03:30,000 El motivo de la implementación de MGSQL como motor de bases de datos tiene que ver con la 39 00:03:30,000 --> 00:03:36,960 cualidad de relacional que tiene el propio motor. Son varias tablas que tienen relaciones de foreign 40 00:03:36,960 --> 00:03:42,240 key entre ellas y además es uno de los motores de base de datos que se integra mejor con API REST y con Spring Boot. 41 00:03:42,240 --> 00:03:43,240 y con Spring Boot 42 00:03:43,240 --> 00:03:46,419 para la autenticación utilicé 43 00:03:46,419 --> 00:03:48,939 JWT o JSON Web Tokens 44 00:03:48,939 --> 00:03:50,719 a grandes rasgos 45 00:03:50,719 --> 00:03:52,780 esta clase lo que hace es 46 00:03:52,780 --> 00:03:54,759 el gestor de autenticación 47 00:03:54,759 --> 00:03:56,300 mediante JWT en la aplicación 48 00:03:56,300 --> 00:03:58,680 su función principal sería generar 49 00:03:58,680 --> 00:03:59,599 los tokens de acceso 50 00:03:59,599 --> 00:04:02,439 cuando el usuario 51 00:04:02,439 --> 00:04:03,580 inicia sesión correctamente 52 00:04:03,580 --> 00:04:06,360 y posteriormente verificar que esos tokens sean válidos 53 00:04:06,360 --> 00:04:07,159 en cada petición 54 00:04:07,159 --> 00:04:10,520 para que nadie sin haber 55 00:04:10,520 --> 00:04:15,060 que ha hecho el inicio de sesión pueda acceder a la base de datos ni a la lógica de negocio. 56 00:04:16,560 --> 00:04:22,279 Además, se puede extraer información del usuario almacenada dentro del token, como el email o el rol. 57 00:04:24,459 --> 00:04:28,839 Esta clase centraliza toda la lógica relacionada con JWT dentro de la API. 58 00:04:29,399 --> 00:04:32,240 Se utiliza para implementar la autenticación stateless en Spring Boot. 59 00:04:32,839 --> 00:04:38,740 Cuando un usuario inicia sesión, la clase genera un token firmado digitalmente que contiene su identidad y permisos. 60 00:04:39,600 --> 00:04:42,600 Posteriormente, en la petición protegida, el sistema valida el token 61 00:04:42,600 --> 00:04:48,439 y recupera la información del usuario sin necesidad de mantener sesiones en el servidor. 62 00:04:49,959 --> 00:04:54,060 Este sistema utiliza la firma HMAC SHA-256 63 00:04:54,060 --> 00:05:00,399 y expiración temporal para garantizar la seguridad en la arquitectura REST stateless. 64 00:05:03,300 --> 00:05:08,420 Para la comunicación en tiempo real, utilicé WebSockets con el protocolo STOMP 65 00:05:08,420 --> 00:05:10,800 que es el Spring que tiene integrado de forma nativa. 66 00:05:11,740 --> 00:05:16,139 Básicamente es un canal persistente entre el servidor y cada aplicación por donde viajan los eventos. 67 00:05:16,139 --> 00:05:21,639 Que el conductor aceptó, que el viaje empezó, que el viaje terminó y demás. 68 00:05:23,199 --> 00:05:28,220 La mayor parte de lógica detrás del protocolo STOMP están en las aplicaciones. 69 00:05:29,699 --> 00:05:36,980 Por ejemplo, aquí en MyPilotDriver tenemos un método que construye el canal 70 00:05:36,980 --> 00:05:46,879 por el que va a estar escuchando todos los mensajes que manda la API, y cómo se desconecta. 71 00:05:46,879 --> 00:05:54,480 Y en clases como el LoginViewModel es que tenemos la implementación de los métodos, 72 00:05:54,480 --> 00:06:04,180 por ejemplo, que tenemos en SocketManager, en los que espera la respuesta de la API con 73 00:06:04,180 --> 00:06:12,180 respecto al token del login, y si es exitoso pues true, y si no, pues dar la advertencia correspondiente. 74 00:06:16,180 --> 00:06:23,180 Para los mapas y las rutas, utilicé la Google Maps Platform, el SDK de mapas, la Direction 75 00:06:23,180 --> 00:06:29,180 SAPI para calcular las rutas, y la Place SAPI para el autocompletado de direcciones. 76 00:06:29,180 --> 00:06:37,000 las llamadas http las hago con retrofit y el cliente stomp en android es una librería que 77 00:06:37,000 --> 00:06:45,379 se llama crossfit, podemos ver retrofit como se usa por ejemplo aquí en api service 78 00:06:45,379 --> 00:06:52,100 o en el propio repositorio de mapas o en el de viajes 79 00:06:52,100 --> 00:07:05,670 Compose, Boot, Pack, eso simplemente conecta lo que es la aplicación con los endpoints de la API. 80 00:07:09,089 --> 00:07:14,850 Las dos aplicaciones están escritas en Kotlin, las aplicaciones de Android, 81 00:07:15,329 --> 00:07:19,870 están escritas en Kotlin y utilizan Jetpack Compose, que es el sistema de UI moderno de Android. 82 00:07:19,870 --> 00:07:34,410 Este sistema permite generar componentes, animaciones, cambios de color, cambios de estado en cada uno de los componentes de la UI de una forma muy directa y muy sencilla. 83 00:07:38,529 --> 00:07:49,250 El proyecto lo desarrollé en fases. Primero te debe construir el backend completo, las entidades de la base de datos, de la lógica de API REST y toda la lógica de estados de viaje. 84 00:07:49,870 --> 00:07:51,649 Como podemos ver aquí, son cinco. 85 00:07:53,110 --> 00:07:57,870 Solicitado, conductor en camino, en curso, finalizado y cancelado. 86 00:07:58,529 --> 00:08:05,110 Las transiciones entre estados están validadas en el servidor, así que no podés pasar directamente de solicitado a finalizado, por ejemplo. 87 00:08:05,350 --> 00:08:06,949 Se puede pasar de solicitado a cancelado. 88 00:08:08,110 --> 00:08:09,750 Esto lo validamos en el service. 89 00:08:09,750 --> 00:08:23,350 En el método de cambiar de estado en un momento se llama a Extransición Válida 90 00:08:23,350 --> 00:08:29,790 Que es otro método que verifica efectivamente de que se cambie el estado de forma natural 91 00:08:29,790 --> 00:08:37,700 Después desarrollé la app del viajero 92 00:08:37,700 --> 00:08:41,899 Todo lo que es el mapa, la búsqueda del destino, el flujo de la solicitud 93 00:08:41,899 --> 00:08:46,659 Recién después de eso, integré los WebSockets para que todo fuera en tiempo real 94 00:08:46,659 --> 00:08:52,320 Y finalmente agregué la autentificación con cota WT 95 00:08:52,320 --> 00:08:57,539 Ya para terminar, desarrollé MyPilotDriver 96 00:08:57,539 --> 00:09:01,940 La disponibilidad, la cola de conductores, el flujo de aceptación y la gestión del viaje 97 00:09:01,940 --> 00:09:11,580 El proyecto simplemente me parecía más sencillo y más lógico 98 00:09:11,580 --> 00:09:15,519 Empezar por el backend y probar con Postman antes de tener apps 99 00:09:15,519 --> 00:09:18,000 Porque el hecho de tener otra aplicación 100 00:09:18,000 --> 00:09:20,000 Añadía otra capa de debugueo 101 00:09:20,000 --> 00:09:22,980 Antes de que las cosas estuvieran realmente bien programadas 102 00:09:22,980 --> 00:09:28,299 Obviamente del dicho al hecho hay un trecho 103 00:09:28,299 --> 00:09:30,240 Realmente me había tomado conciencia 104 00:09:30,240 --> 00:09:32,220 De la magnitud del proyecto 105 00:09:32,220 --> 00:09:35,279 Teniendo en cuenta que son tres aplicaciones distintas 106 00:09:35,279 --> 00:09:38,759 Si bien sí tiene mucho en común actuar en conjunto 107 00:09:38,759 --> 00:09:40,299 Pero son tres aplicaciones 108 00:09:40,299 --> 00:09:43,399 Y evidentemente siendo un solo integrante 109 00:09:43,399 --> 00:09:52,820 el plan sufrió muchos cambios y yo pensaba que la API ya estaba terminada y el backend estaba 110 00:09:52,820 --> 00:10:00,019 completo y funcionaba perfecto y al pensar en una nueva funcionalidad o una funcionalidad necesaria 111 00:10:00,019 --> 00:10:06,639 que se me había olvidado al desarrollar algunas aplicaciones de las aplicaciones Kotlin teníamos 112 00:10:06,639 --> 00:10:13,700 que teníamos. Tenía que volver a desarrollar el backend de otra forma, teniendo en cuenta 113 00:10:13,700 --> 00:10:20,980 otros parámetros en pos de otra idea. Así que fue un vaivén bastante importante de 114 00:10:20,980 --> 00:10:22,879 bastantes veces el proyecto. 115 00:10:24,200 --> 00:10:30,059 Si tuviera que destacar tres cosas técnicas del proyecto serían las siguientes. Una de 116 00:10:30,059 --> 00:10:35,279 ellas es la cola de conductores en la memoria. Cuando un viajero solicita un viaje, el sistema 117 00:10:35,279 --> 00:10:40,100 notifica a los primeros 10 conductores disponibles, ordenados por tiempo de espera. El primero 118 00:10:40,100 --> 00:10:44,639 en aceptar se le asigna el viaje y sale de la cola. Si lo rechaza, el sistema ofrece 119 00:10:44,639 --> 00:10:49,139 el viaje al siguiente y cuando el viaje termina, el conductor vuelve automáticamente al final 120 00:10:49,139 --> 00:10:56,440 de la cola. Inicializa la lista a partir de la base de datos y si en algún momento 121 00:10:56,440 --> 00:11:02,500 un conductor se da de alta, o sea, activa el Available dentro de la aplicación, lo 122 00:11:02,500 --> 00:11:08,159 mete a esperar en la cola. Una vez se le asigna el viaje se llama al 123 00:11:08,159 --> 00:11:18,539 método eliminar para sacarlo de la cola e incorporar si es que 124 00:11:18,539 --> 00:11:23,419 reincorporar es básicamente lo mismo que registrar. 125 00:11:23,419 --> 00:11:29,299 La segunda y la tercera serían la implementación de la API de Google en la 126 00:11:29,299 --> 00:11:31,340 aplicación de MyPilot y MyPilotDriver 127 00:11:31,340 --> 00:11:33,259 porque el hecho 128 00:11:33,259 --> 00:11:35,220 de contar con una API de Google 129 00:11:35,220 --> 00:11:37,419 ya tenía aparte 130 00:11:37,419 --> 00:11:38,720 otro proceso de trabajo 131 00:11:38,720 --> 00:11:41,379 tu quedar de alta en una 132 00:11:41,379 --> 00:11:42,480 cuenta 133 00:11:42,480 --> 00:11:45,360 generar la API key secreta 134 00:11:45,360 --> 00:11:47,460 de Google, generar una cuota 135 00:11:47,460 --> 00:11:49,240 de uso máxima 136 00:11:49,240 --> 00:11:51,139 para la API para que el proyecto 137 00:11:51,139 --> 00:11:53,019 siga siendo gratis y 138 00:11:53,019 --> 00:11:54,279 académicamente viable 139 00:11:54,279 --> 00:11:57,179 y la tercera 140 00:11:57,179 --> 00:11:59,279 cosa sería la implementación de esta 141 00:11:59,279 --> 00:12:02,419 API de Google en conjunto 142 00:12:02,419 --> 00:12:04,600 con la arquitectura 143 00:12:04,600 --> 00:12:06,460 de las apps, el ModelViewViewModel 144 00:12:06,460 --> 00:12:08,360 donde ViewModel 145 00:12:08,360 --> 00:12:10,559 expone el estado de forma reactiva 146 00:12:10,559 --> 00:12:12,639 y Compose redibuja 147 00:12:12,639 --> 00:12:14,460 la UI automáticamente cuando algo 148 00:12:14,460 --> 00:12:16,139 cambia. Además 149 00:12:16,139 --> 00:12:18,480 destacó Datastore para persistir 150 00:12:18,480 --> 00:12:20,019 la sesión ante reinicios de la app 151 00:12:20,019 --> 00:12:23,509 teniendo en cuenta las tokens. 152 00:12:26,370 --> 00:12:27,370 Durante el desarrollo del 153 00:12:27,370 --> 00:12:29,470 proyecto hubo varios problemas interesantes 154 00:12:29,470 --> 00:12:31,409 el principal fue de hardware 155 00:12:31,409 --> 00:12:36,690 que no podía correr dos emuladores al mismo tiempo en la computadora 156 00:12:36,690 --> 00:12:40,649 y además tener IntelliJ abierto con el backend corriendo para probarlo. 157 00:12:41,409 --> 00:12:45,009 La solución que encontré fue simplemente exportarlo como HA al backend 158 00:12:45,009 --> 00:12:47,549 y ejecutarlo directamente desde el terminal. 159 00:12:48,049 --> 00:12:49,830 Consumía muchísimo menos memoria. 160 00:12:50,889 --> 00:12:53,409 Además, durante gran parte del desarrollo utilicé Postman 161 00:12:53,929 --> 00:13:00,330 con las solicitudes que haría el segundo emulador. 162 00:13:00,330 --> 00:13:05,269 si tenía el MyPilot abierto, emulaba MyPilotDriver y viceversa. 163 00:13:06,149 --> 00:13:07,970 El segundo problema fue con los WebSockets. 164 00:13:08,950 --> 00:13:11,750 Al tratar de probarlo desde Android me tiraba al 400. 165 00:13:12,850 --> 00:13:17,429 No me había dado cuenta de que por defecto Spring activa SockJS, 166 00:13:17,929 --> 00:13:21,610 que es una capa de compatibilidad que choca con Android. 167 00:13:22,830 --> 00:13:27,269 La solución fue simplemente deshabilitar SockJS y utilizar WebSocket puro. 168 00:13:27,269 --> 00:13:34,190 El último problema que tuve, que parece ser un clásico de Android, fue que los mensajes 169 00:13:34,190 --> 00:13:38,269 de WebSocket llegaban bien a la aplicación, los veía en el log, pero no se actualizaba 170 00:13:38,269 --> 00:13:46,269 la UI. El problema principal era que el callback se estaba ejecutando en el hilo de I.O. y 171 00:13:46,269 --> 00:13:51,269 no en el principal. La solución fue forzar que se ejecute en el hilo principal con Dispatches 172 00:13:51,269 --> 00:13:52,269 Main. 173 00:13:52,269 --> 00:14:00,870 Tenemos a la derecha la aplicación MyPilot, a la izquierda Postman y una terminal ejecutando la API 174 00:14:00,870 --> 00:14:05,710 Iniciamos la sesión como James Walker que es un viajero que tenemos en la base de datos 175 00:14:05,710 --> 00:14:07,950 Vemos que hace realmente la petición 176 00:14:07,950 --> 00:14:10,029 Esperamos que cargue la pantalla 177 00:14:10,029 --> 00:14:15,220 Permitimos el uso de la ubicación en el teléfono 178 00:14:15,220 --> 00:14:18,600 Esto ya es una cosa del emulador 179 00:14:18,600 --> 00:14:22,559 Por defecto define la ubicación como la oficina central de Google 180 00:14:22,559 --> 00:14:23,940 Yo no estoy en California. 181 00:14:24,480 --> 00:14:28,940 Podemos seleccionar cualquier parte del mapa o simplemente apretar el botón Take Me Home. 182 00:14:29,460 --> 00:14:35,320 Ese botón carga de la base de datos al mapa la ubicación del hogar de la persona que está en sesión. 183 00:14:36,220 --> 00:14:44,320 Todas las solicitudes que estamos haciendo las estamos haciendo en Postman utilizando el token de Login como una variable alojada en el entorno Collection de Postman. 184 00:14:44,679 --> 00:14:48,399 Así podemos emular una autenticación válida para el Get que estamos haciendo. 185 00:14:48,740 --> 00:14:52,159 Hacemos este Get para verificar que realmente se está creando un nuevo viaje. 186 00:14:52,559 --> 00:14:57,159 creamos la solicitud del viaje con viajar ahora 187 00:14:57,159 --> 00:15:02,860 y podemos ver en la API que realmente se hace la petición 188 00:15:02,860 --> 00:15:13,029 ahí vemos el viaje nuevo creado con su nuevo ID 189 00:15:13,029 --> 00:15:17,710 y ahora lo que vamos a hacer es cambiar la petición a post 190 00:15:17,710 --> 00:15:21,070 para asignar el conductor a este viaje solicitado 191 00:15:21,070 --> 00:15:24,850 en este caso evidentemente el token que estamos usando 192 00:15:24,850 --> 00:15:32,769 es de un login de un rol conductor, más específicamente el conductor con los datos de nombre Carlos Ramírez. 193 00:15:35,090 --> 00:15:43,409 Y ahí podemos ver efectivamente cómo sin tocar nada cambia el banner de la app de MyPilot 194 00:15:43,409 --> 00:15:47,470 y efectivamente cambia el estado del viaje a conductor en camino. 195 00:15:48,370 --> 00:16:02,330 Hacemos el cambio a PUT para cambiar el estado del viaje a iniciar. 196 00:16:02,330 --> 00:16:08,169 y ahí vemos que tanto en la UI como en la base de datos 197 00:16:08,169 --> 00:16:10,950 el estado del viaje ha cambiado en curso 198 00:16:10,950 --> 00:16:22,490 cambiamos el estado de viaje finalizado y nos sale el banner de calificar al conductor 199 00:16:22,490 --> 00:16:25,029 y guardamos 200 00:16:25,029 --> 00:16:28,690 y ese sería todo el ciclo de vida de un viaje de MyPilot 201 00:16:28,690 --> 00:16:32,309 esencialmente MyPilotDriver es muy parecido 202 00:16:32,309 --> 00:16:35,309 simplemente cambia un poco la UI porque no tiene la barra de búsqueda 203 00:16:35,309 --> 00:16:40,230 sino que tiene un switch que al activarlo empieza a escuchar viajes nuevos en la API 204 00:16:40,230 --> 00:16:43,769 usamos aquí un post de un viaje que yo tenía ya preparado 205 00:16:43,769 --> 00:16:46,470 y vemos como de nuevo sin tocar nada 206 00:16:46,470 --> 00:16:49,830 el banner cambia y el estado del viaje aparece 207 00:16:49,830 --> 00:16:51,029 porque es un viaje nuevo 208 00:16:51,029 --> 00:16:54,029 apretamos aceptar, esperamos que cargue 209 00:16:54,029 --> 00:16:57,009 y podemos ver como el estado cambia a ongoing trip 210 00:16:57,009 --> 00:17:00,669 lo que pasa es que realmente el estado del viaje cambió a conductor en camino 211 00:17:00,669 --> 00:17:03,769 en la pantalla podemos ver como se trazan las líneas de directions 212 00:17:03,769 --> 00:17:07,789 hacia el punto de inicio del viaje solicitado 213 00:17:07,789 --> 00:17:10,109 porque tiene que ir a buscar al pasajero 214 00:17:10,109 --> 00:17:15,809 Una vez suponiendo que hayamos llegado ya a buscar al pasajero 215 00:17:15,809 --> 00:17:18,269 Apretamos el botón de Let's Pilot 216 00:17:18,269 --> 00:17:20,329 Que inicia el viaje 217 00:17:20,329 --> 00:17:23,069 Y podemos ver como el mapa se limpia 218 00:17:23,069 --> 00:17:26,730 Y marca la ubicación del destino del viajero 219 00:17:26,730 --> 00:17:29,910 Google Directions carga desde la ubicación del propio teléfono 220 00:17:29,910 --> 00:17:31,509 O el emulador en este caso 221 00:17:31,509 --> 00:17:33,970 Por eso es que está marcándolo desde tan lejos 222 00:17:33,970 --> 00:17:37,049 Una vez apretamos el botón de finalizar viaje 223 00:17:37,049 --> 00:17:38,410 O End Trip o End Journey 224 00:17:38,410 --> 00:17:40,690 damos por terminado el flujo 225 00:17:40,690 --> 00:17:42,109 natural de 226 00:17:42,109 --> 00:17:43,809 MyPilotDriver 227 00:17:43,809 --> 00:17:46,849 En conclusión, si bien este 228 00:17:46,849 --> 00:17:49,210 proyecto fue un reto, fue algo desafiante 229 00:17:49,210 --> 00:17:50,609 fue muy entretenido 230 00:17:50,609 --> 00:17:52,009 me enseñó muchísimo 231 00:17:52,009 --> 00:17:54,950 y me dieron muchas ganas 232 00:17:54,950 --> 00:17:56,630 de seguir desarrollando la app 233 00:17:56,630 --> 00:17:58,650 incluso después de terminar el TFG 234 00:17:58,650 --> 00:18:00,609 y entregarlo, porque creo que realmente 235 00:18:00,609 --> 00:18:02,470 tiene futuro, es una app que 236 00:18:02,470 --> 00:18:04,009 es 1. monetizable 237 00:18:04,009 --> 00:18:06,410 2. resolvió un problema 238 00:18:06,410 --> 00:18:08,730 real y específico 239 00:18:08,730 --> 00:18:10,289 y cotidiano en la vida 240 00:18:10,289 --> 00:18:11,049 de cualquier persona 241 00:18:11,049 --> 00:18:13,509 y tiene mucho potencial 242 00:18:13,509 --> 00:18:16,710 pero en lo que al TFG respecta 243 00:18:16,710 --> 00:18:18,049 ya estaríamos llegando al final 244 00:18:18,049 --> 00:18:19,549 muchísimas gracias por ver 245 00:18:19,549 --> 00:18:20,910 hasta luego, un saludo