Docsity
Docsity

Prepara tus exámenes
Prepara tus exámenes

Prepara tus exámenes y mejora tus resultados gracias a la gran cantidad de recursos disponibles en Docsity


Consigue puntos base para descargar
Consigue puntos base para descargar

Gana puntos ayudando a otros estudiantes o consíguelos activando un Plan Premium


Orientación Universidad
Orientación Universidad


economicallyapppara personas, Apuntes de Tecnología

sitve para estudiar como aplicacion para personas que quieren programar algo que puedan hacer desde su casa sitve para estudiar como aplicacion para personas que quieren programar algo que puedan hacer desde su casa sitve para estudiar como aplicacion para personas que quieren programar algo que puedan hacer desde su casa sitve para estudiar como aplicacion para personas que quieren programar algo que puedan hacer desde su casa sitve para estudiar como aplicacion para personas que quieren programar algo que puedan hacer desde su casa sitve para estudiar como aplicacion para personas que quieren programar algo que puedan hacer desde su casa sitve para estudiar como aplicacion para personas que quieren programar algo que puedan hacer desde su casa sitve para estudiar como aplicacion para personas que quieren programar algo que puedan hacer desde su casa sitve para estudiar como aplicacion para personas que quieren programar algo que puedan hauedan hacer desde su casa sitve para estudiçdr

Tipo: Apuntes

2024/2025

Subido el 16/12/2025

luigi-villamizar
luigi-villamizar 🇪🇸

1 documento

1 / 32

Toggle sidebar

Esta página no es visible en la vista previa

¡No te pierdas las partes importantes!

bg1
EconomicallyE
Impulsa tus ahorros con el poder de la IA
Trabajo final de grado.
Tutor: Carlos Gonzales.
Integrantes: José Angel López, Ramiro Cueva.
Curso: 2ºDAW 2024-2025
10/06/2025
pf3
pf4
pf5
pf8
pf9
pfa
pfd
pfe
pff
pf12
pf13
pf14
pf15
pf16
pf17
pf18
pf19
pf1a
pf1b
pf1c
pf1d
pf1e
pf1f
pf20

Vista previa parcial del texto

¡Descarga economicallyapppara personas y más Apuntes en PDF de Tecnología solo en Docsity!

EconomicallyE

Impulsa tus ahorros con el poder de la IA

Trabajo final de grado.

Tutor: Carlos Gonzales.

Integrantes: José Angel López, Ramiro Cueva.

Curso: 2ºDAW 2024-

Índice

  • Ciclo: 2 DAW
  • 1 Contenido del documento - 1.1 Introducción..................................................................... - 1.2 Justificación del proyecto y objetivos............................. - 1.3 Planificación..................................................................... - 1.4 Parte experimental......................................................... - 1.4.1 Análisis:¿Qué hará la aplicación?......................... - 1.4.2 Diseño. ¿Cómo se hará la aplicación?................... - 1.4.3 Implementación y pruebas..................................... - 1.4.4 Implantación y documentación.............................. - 1.4.5 Resultados y discusión........................................... - 1.5 Conclusiones..................................................................... - 1.6 Bibliografía y referencias................................................... - 1.7 Anexos............................................................................

Ciclo: 2 DAW 2025

proyecto nace con la necesidad de ayudar a aquellas personas que siempre

planean ahorrar algo a fin de mes y se les complica seguir sus gastos para lograr

sus cometidos.

Estado del arte:

Mercado:

Actualmente el sector económico ha decaído mucho después de la pandemia, lo

cual ha generado cierta necesidad de la población por ahorrar influenciada

también por las redes sociales en cuanto a la atención sobre la economía

personal.

Necesidades:

Falta de educación financiera en la población y el querer empezar a tener un plan

ahorrativo

Productos existentes:

 Calculadoras financieras

 Calculadora N

 Fintonic

 Mint

A pesar de los productos existentes, ninguno ofrece recomendaciones

personalizadas y la mayoría de las aplicaciones requiere de un cierto

conocimiento en finanzas. Nuestra diferenciación es que nuestra aplicación busca

ayudar y a planificar un plan de ahorro a nuestros clientes de manera

personalizada

Objetivos:

 Ayudar a nuestros usuarios a tener un incremento de ahorro mediante

control de gastos

 Seguimiento personalizado aconsejado mediante la IA

 Interfaz fácil de usar permitiendo al cliente actualizar datos

Ciclo: 2 DAW 2025

Perfil público al que se dirige:

A pesar de que la aplicación está pensada en ser dirigida a cualquier tipo de

publico que busque ahorrar, mayormente va enfocada en jóvenes los cuales están

teniendo su primer trabajo y tienen poca experiencia en cómo poder manejar sus

ingresos.

1.3. Planificación

Historias de usuario:

1. Como usuario nuevo:

Quiero registrarme en la aplicación con mi correo y una contraseña propia,

acceder a mi perfil y poder agregar y gestionar mis datos personales

2. Como usuario ya registrado:

Quiero poder ver mis datos y poder cambiar las fechas, parámetros, cantidades

según mis intereses y preferencias

3. Como usuario registrado:

Quiero establecer objetivos a futuro(vacaciones, fondo de emergencia, coche,

teléfono móvil). Para tener metas establecidas

4. Como usuario registrado:

Quiero recibir recomendaciones de ahorro personalizadas en base a mis datos

previamente introducidos

5. Como usuario registrado:

Quiero poder ver mis consejos personalizados con fecha y orden para ir

gestionando mi avance

6. Como usuario registrado:

Quiero ver graficas, estadísticas y diagramas sobre mi progreso, en el que se

muestren mis ahorros contrastados con mis gastos para evaluar mis decisiones, si

estoy cumpliendo metas y adecuar mi comportamiento financiero en caso de que

sea necesario

Ciclo: 2 DAW 2025

Generación de consejos financieros con IA:

 Mediante la base de ingresos, gastos, metas del usuario la app haría una

petición a la API para luego devolver el consejo financiero

 Estas recomendaciones pueden ser consejos sobre cómo ajustar gastos,

cuánto ahorrar mensualmente, o cómo priorizar objetivos.

7.1. Parte experimental

1.4.1 Análisis. ¿Qué hará la aplicación?

La aplicación será un consejero financiero en el sector ahorrativo y personal de

cada usuario, mediante formulario previo el cual se almacenara en una base

de datos utilizada para generar un prompt, la IA obtendrá los datos enviando

este prompt hacia la API de OpenAI, en este caso (Chatgpt4-turbo) la cual se

encargara de dar los consejos personalizados usando los datos de los

usuarios y contexto de los mismos para así ser más precisa.

Dichos consejos también serán guardados en la base de datos para poder

darle al usuario una recomendación basada en su progreso de una manera

más amigable ya que contara con la información necesaria para darle soporte

de manera ahorrativa a nuestros usuarios

La aplicación será un ayudante. En este punto nos referimos a que la

aplicación no controlara tus finanzas ni te dirá en donde invertir, será

meramente un consejero el cual te dirá cuáles son tus posibilidades de ahorro

dependiendo de tus circunstancias económicas

Ciclo: 2 DAW 2025

Diagrama de la base de datos Entidad Relación:

Modelo relacional y tablas a utilizar:

Usuarios: PK id, nombre, email, contraseña, fecha_registro, ingreso_mensual

Recomendaciones: PK id, FK id_usuario, resultadoIA, fecha_recomendacion

Gastos fijos: PK id, FK id_usuario, nombre, monto, frecuencia, descripción

Meta Ahorros: PK id, FK id_usuario, descripción, monto_objetivo,

monto_ahorrado, fecha_meta

Ciclo: 2 DAW 2025

Resumen de perfil:

1.4.2 Diseño. ¿Como se hará la aplicación?

Estructura de base de datos

La aplicación usara una base de datos relacional que almacenara la información

que permitirá utilizar las principales funciones de la aplicación, como la gestión de

usuarios, gastos, ingresos y objetivos financieros.

Las tablas principales serán:

 Usuarios (users): almacena información básica del usuario e inicio de

sesión

 Gastos fijos (fixed expenses): registros de gastos fijos del usuario,

guardando el id del mismo.

Ciclo: 2 DAW 2025

 Gastos variables (variable expenses): aquí se registraran los gastos

variables del usuario.

 Metas de Ahorro (goals): aquí se incluyen las metas de ahorro de cada

usuario junto a su fecha estimada para dicho objetivo.

 Consejos (advices): esta tabla guardara el id del consejo que nos

proporcione la IA mediante los datos del usuario previamente enviados

junto al prompt.

Arquitectura del proyecto

La aplicación seguirá una arquitectura de capas basada en el patrón MVC

(Modelo, Vista, Controlador) porque hemos llegado a la conclusión de que es lo

mejor para la escalabilidad del proyecto:

Ciclo: 2 DAW 2025

Scripts más relevantes:

El flujo de información es importante en nuestra aplicación, por ende nuestras

entidades tienen que estar bien estructuradas con los métodos crud para poder

enviarle el prompt lo mas especificado con los datos del usuario a la IA

Algunos de los servicios con mas lógica y con mayor importancia de nuestra

aplicación:

OpenAiService

Utilizando RestTemplate, se guardan los valores desde el properties con la

anotación @Value, ApiKey, ApiURL, y el modelo que se le asignara a nuestra

inteligencia artificial, en este caso la API de OpenAi Gpt4-Turbo, el cual fue

escogido por su relación entre calidad-precio y su eficiencia en la conversación

mediante texto, también se manejan las excepciones en caso de fallos en la

comunicación con la API

Este servicio permite obtener una respuesta textual desde la IA que se utilizara

como la recomendación personalizada para el usuario

@Service @RequiredArgsConstructor public class OpenAIServiceImpl implements AIService { private final RestTemplate restTemplate; @Value("${openai.api.url}") private String apiUrl; @Value("${openai.api.key}") private String apiKey; @Value("${openai.model}") private String model; @Override public String getAIRecommendation(String prompt) { HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType. APPLICATION_JSON ); headers.setBearerAuth(apiKey); Map<String, Object> requestBody = new HashMap<>(); requestBody.put("model", model); requestBody.put("messages", List. of ( Map. of ("role", "user", "content", prompt) )); requestBody.put("temperature", 0.7); requestBody.put("max_tokens", 1000 ); HttpEntity<Map<String, Object>> entity = new HttpEntity<>(requestBody, headers); try {

Ciclo: 2 DAW 2025

ResponseEntity response = restTemplate.exchange(apiUrl, HttpMethod. POST , entity, Map.class); // Procesar siempre el cuerpo, incluso si status no es 2xx if (response != null && response.getStatusCode().is2xxSuccessful() && response.getBody() != null) { Object choicesObj = response.getBody().get("choices"); if (choicesObj instanceof List choices && !choices.isEmpty()) { Object firstChoice = choices.get( 0 ); if (firstChoice instanceof Map firstChoiceMap) { Object messageObj = firstChoiceMap.get("message"); if (messageObj instanceof Map messageMap) { Object content = messageMap.get("content"); if (content instanceof String) { return (String) content; } } } } // Si choices existe pero está vacío, devolver mensaje informativo return "La API de OpenAI no devolvió recomendaciones. Intente cambiar el prompt o revisar los parámetros del modelo."; } else if (response != null && response.getBody() != null && response.getBody().containsKey("error")) { Map error = (Map) response.getBody().get("error"); String message = error.getOrDefault("message", "Error desconocido").toString(); System. err .println("Error de OpenAI: " + message); throw new RuntimeException("Error de OpenAI: " + message); } else { System. err .println("Respuesta inesperada de OpenAI: " + (response != null? response.toString() : "null")); throw new RuntimeException("Respuesta inesperada de OpenAI."); } } catch (HttpStatusCodeException httpEx) { String responseBody = httpEx.getResponseBodyAsString(); System. err .println("HTTP error al comunicarse con OpenAI API: " + httpEx.getStatusCode() + " - Body: " + responseBody); throw new RuntimeException("Error HTTP al comunicarse con OpenAI API: " + httpEx.getStatusCode(), httpEx); } catch (RestClientException rce) { System. err .println("Problema de red o RestClient al comunicarse con OpenAI API: " + rce.getMessage()); throw new RuntimeException("Problema de red o RestTemplate en la comunicación con OpenAI API", rce); } catch (Exception e) { System. err .println("Error general en la comunicación con OpenAI API: " + e.getMessage()); throw new RuntimeException("Error general en la comunicación con OpenAI API", e); } } }

Ciclo: 2 DAW 2025

// Validación: El usuario debe tener ingresos mensuales if (user.getMonthlyIncome() == null || user.getMonthlyIncome() <= 0 ) { return buildErrorAdvice(user.getId(), localeResolver.resolveLocale(request).getLanguage().equals("es") ? "Debes configurar tus ingresos mensuales antes de generar un consejo financiero." : "You must configure your monthly income before generating financial advice."); } Optional lastAdvice = adviceRepository.findTopByUserIdOrderByRecommendationDateDesc(user.getId( )); // Validación 1: Verificar si hay cambios en los datos del usuario boolean dataChanged = lastAdvice.isEmpty() || hasUserDataChanged(lastAdvice.get(), user, currentFixedExpenses, currentVariableExpenses, currentGoals); // Validación 2: Verificar si el último consejo fue hace menos de 1 semana boolean isTooRecent = lastAdvice.isPresent() && lastAdvice.get().getRecommendationDate() .isAfter(LocalDateTime. now ().minusWeeks( 1 )); if (lastAdvice.isPresent()) { if (!dataChanged) { return buildErrorAdvice(user.getId(), localeResolver.resolveLocale(request).getLanguage().equals("es") ? "Debes actualizar tu información financiera antes de recibir un nuevo consejo." : "You must update your financial information before receiving new advice."); } if (isTooRecent) { return buildErrorAdvice(user.getId(), localeResolver.resolveLocale(request).getLanguage().equals("es") ? "Debes esperar al menos una semana desde tu último consejo para recibir uno nuevo." : "You must wait at least one week from your last advice to receive a new one."); } } // Generar el consejo Locale locale = localeResolver.resolveLocale(request); String prompt = buildPrompt(user, currentFixedExpenses, currentVariableExpenses, currentGoals, questionnaire.getPlannedSavings(), locale); // Log del prompt que se envía a la IA logPromptDetails(prompt, locale); String iaResult; try { iaResult = aiService.getAIRecommendation(prompt);

Ciclo: 2 DAW 2025

} catch (Exception e) { iaResult = locale.getLanguage().equals("es") ? "No se pudo generar un consejo en este momento. Inténtalo más tarde." : "Could not generate advice at this time. Please try again later."; } String dataHash = calculateUserDataHash(user, currentFixedExpenses, currentVariableExpenses, currentGoals); String adviceWithHash = "[DATAHASH:" + dataHash + "]\n" + iaResult; Advice advice = Advice. builder () .user(user) .iaResult(iaResult) .recommendationDate(LocalDateTime. now ()) .dataHash(dataHash) .build(); advice = adviceRepository.save(advice); return adviceMapper.toDto(advice); } private String buildPrompt(User user, List fixedExpenses, List variableExpenses, List goals, Double plannedSavings, Locale locale) { StringBuilder prompt = new StringBuilder(); String dataHash = calculateUserDataHash(user, fixedExpenses, variableExpenses, goals); if (locale.getLanguage().equals(new Locale("es").getLanguage())) { prompt.append("Por favor, responde completamente en español.\n\n"); } else { prompt.append(" (IMPORTANT) Please respond entirely in English.\n\n"); } prompt.append("Eres un asesor financiero experto con un enfoque en coaching financiero. Tu objetivo es proporcionar recomendaciones extremadamente prácticas y personalizadas que ayuden al usuario a optimizar sus finanzas sin caer en recortes innecesarios.\n\n"); prompt.append("### CONTEXTO CLAVE QUE DEBES CONSIDERAR ###\n"); prompt.append("- El usuario tiene ingresos mensuales estables de ").append(user.getMonthlyIncome()).append(" €.\n"); prompt.append("- Después de cubrir sus gastos fijos y variables conocidos, le quedan aproximadamente ").append(calculateRemainingMoney(user, fixedExpenses, variableExpenses)).append(" € mensuales libres.\n"); prompt.append("- Esto significa que, si bien hay margen para pequeños ajustes, el usuario ya está en una buena posición para ahorrar sin tener que hacer grandes sacrificios.\n"); prompt.append("- No insistas en eliminar todos los gastos si no es necesario. En su lugar, enseña cómo organizar y aprovechar mejor su dinero libre para alcanzar sus metas.\n"); prompt.append("- El objetivo principal no es solo reducir, sino equilibrar: mantener calidad de vida, reducir gastos innecesarios solo cuando tenga sentido y planificar activamente el ahorro.\n"); prompt.append("- Ten en cuenta que hay gastos que no están detallados (como comida o transporte), por lo que no asumas que el dinero restante

Ciclo: 2 DAW 2025

Documentación de las pruebas realizadas:

Test de comprobación usando Mock

package es.jose.economicallye; @ExtendWith(MockitoExtension.class) public class AdviceServiceImplUnitTest { @Test void generateAdvice_validQuestionnaire_shouldReturnAdviceDTO() { Long userId = 1L; FinancialQuestionnaireDTO questionnaire = FinancialQuestionnaireDTO. builder ().userId(userId).plannedSavings(100.0). build(); User user = User. builder ().id(userId).name("Test User").monthlyIncome(3000.0).build(); List fixedExpenses = Collections. singletonList (FixedExpense. builder ().id(1L).user(user).name(" Rent").amount(1000.0).frequency("Monthly").build()); List variableExpenses = Collections. singletonList (VariableExpense. builder ().id(1L).user(user).nam e("Groceries").amount(300.0).expenseDate(LocalDate. now ()).build()); List goals = Collections. singletonList (Goal. builder ().id(1L).user(user).description("N ew Laptop").targetAmount(1500.0).savedAmount(500.0).deadline(LocalDate. now () .plusMonths( 6 )).build()); String aiRecommendation = "Genial consejo"; Advice savedAdvice = Advice. builder ().id(1L).user(user).iaResult(aiRecommendation).recommendat ionDate(LocalDateTime. now ()).build(); AdviceDTO expectedAdviceDTO = AdviceDTO. builder ().id(1L).userId(userId).iaResult(aiRecommendation).reco mmendationDate(savedAdvice.getRecommendationDate()).build(); when (userRepository.findById(userId)).thenReturn(Optional. of (user)); when (fixedExpenseRepository.findByUserId(userId)).thenReturn(fixedExpense s); when (variableExpenseRepository.findByUserId(userId)).thenReturn(variableE xpenses); when (goalRepository.findByUserId(userId)).thenReturn(goals); when (aiService.getAIRecommendation( anyString ())).thenReturn(aiRecommendat ion); when (adviceRepository.save( any (Advice.class))).thenReturn(savedAdvice); when (adviceMapper.toDto(savedAdvice)).thenReturn(expectedAdviceDTO); AdviceDTO actualAdviceDTO = adviceService.generateAdvice(questionnaire);

Ciclo: 2 DAW 2025

assertNotNull (actualAdviceDTO); assertEquals (expectedAdviceDTO.getId(), actualAdviceDTO.getId()); assertEquals (expectedAdviceDTO.getUserId(), actualAdviceDTO.getUserId()); assertEquals (expectedAdviceDTO.getIaResult(), actualAdviceDTO.getIaResult()); assertEquals (expectedAdviceDTO.getRecommendationDate().toLocalDate(), actualAdviceDTO.getRecommendationDate().toLocalDate()); verify (userRepository, times ( 1 )).findById(userId); verify (fixedExpenseRepository, times ( 1 )).findByUserId(userId); verify (variableExpenseRepository, times ( 1 )).findByUserId(userId); verify (goalRepository, times ( 1 )).findByUserId(userId); verify (aiService, times ( 1 )).getAIRecommendation( anyString ()); verify (adviceRepository, times ( 1 )).save( any (Advice.class)); verify (adviceMapper, times ( 1 )).toDto(savedAdvice); } @Test void generateAdvice_nullUserId_shouldThrowIllegalArgumentException() { FinancialQuestionnaireDTO questionnaire = FinancialQuestionnaireDTO. builder ().build(); assertThrows (IllegalArgumentException.class, () - > adviceService.generateAdvice(questionnaire)); verifyNoInteractions (userRepository); verifyNoInteractions (fixedExpenseRepository); verifyNoInteractions (variableExpenseRepository); verifyNoInteractions (goalRepository); verifyNoInteractions (aiService); verifyNoInteractions (adviceRepository); verifyNoInteractions (adviceMapper); } @Test void generateAdvice_userNotFound_shouldThrowUserNotFoundException() { Long userId = 1L; FinancialQuestionnaireDTO questionnaire = FinancialQuestionnaireDTO. builder ().userId(userId).plannedSavings(100.0). build(); when (userRepository.findById(userId)).thenReturn(Optional. empty ()); assertThrows (UserNotFoundException.class, () - > adviceService.generateAdvice(questionnaire)); verify (userRepository, times ( 1 )).findById(userId); verifyNoInteractions (fixedExpenseRepository); verifyNoInteractions (variableExpenseRepository); verifyNoInteractions (goalRepository); verifyNoInteractions (aiService); verifyNoInteractions (adviceRepository); verifyNoInteractions (adviceMapper); } @Test void getAdviceHistory_nullUserId_shouldThrowIllegalArgumentException() {