From 41c40265cf55531616fed2832bc4ade81ce8e269 Mon Sep 17 00:00:00 2001 From: "Mauro Rosero P." Date: Wed, 12 Mar 2025 14:06:42 -0500 Subject: [PATCH] =?UTF-8?q?[IMPROVED]=20Usar=20tarifa=20fullstack.rate=20?= =?UTF-8?q?=C3=97=20176=20como=20costo=20por=20persona-mes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- bin/cocomo.py | 60 ++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 57 insertions(+), 3 deletions(-) diff --git a/bin/cocomo.py b/bin/cocomo.py index b866759..c48be0f 100755 --- a/bin/cocomo.py +++ b/bin/cocomo.py @@ -31,6 +31,11 @@ logger.addHandler(console_handler) # Variable para controlar si se muestra el resultado en la consola SHOW_RESULTS = True +# Directorio base del proyecto +BASE_DIR = Path(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) +CONFIG_DIR = BASE_DIR / 'bin' / 'config' +RATES_DIR = CONFIG_DIR / 'rates' # Ubicación para archivos de tarifas + # Constantes para el modelo COCOMO básico COCOMO_MODELS = { # [a, b, c, d] - Coeficientes para Esfuerzo = a * (KLOC^b) y Tiempo = c * (Esfuerzo^d) @@ -39,6 +44,9 @@ COCOMO_MODELS = { 'embedded': [3.6, 1.20, 2.5, 0.32] # Proyectos complejos } +# Número de horas laborables en un mes (22 días x 8 horas) +HOURS_PER_MONTH = 176 + # Extensiones de archivo a considerar por defecto DEFAULT_EXTENSIONS = [ '.py', '.java', '.c', '.cpp', '.cc', '.cxx', '.h', '.hpp', '.js', '.ts', @@ -242,6 +250,33 @@ def determine_cocomo_model(loc): else: return 'embedded' # Proyectos grandes (más de 300K líneas) +def get_fullstack_rate(): + """ + Obtiene la tarifa por hora de un desarrollador fullstack desde el archivo de configuración. + Si el archivo no existe, devuelve un valor predeterminado de 45.00 USD/hora. + + Returns: + Float: Tarifa por hora en USD + """ + rate_file = RATES_DIR / "fullstack.rate" + + # Comprobar si existe el archivo + if not rate_file.exists(): + logger.warning(f"No se encontró el archivo de tarifa: {rate_file}") + logger.warning("Usando tarifa predeterminada de 45.00 USD/hora") + return 45.00 + + # Leer la tarifa del archivo + try: + with open(rate_file, 'r', encoding='utf-8') as f: + rate = float(f.read().strip()) + logger.info(f"Tarifa leída desde {rate_file}: {rate:.2f} USD/hora") + return rate + except (FileNotFoundError, ValueError, IOError) as e: + logger.warning(f"Error al leer el archivo de tarifa {rate_file}: {e}") + logger.warning("Usando tarifa predeterminada de 45.00 USD/hora") + return 45.00 + def print_results(results): """ Imprime los resultados del cálculo COCOMO de manera formateada. @@ -268,8 +303,8 @@ if __name__ == "__main__": parser.add_argument('--project', required=True, help='Ruta del directorio del proyecto a analizar') parser.add_argument('--model', choices=['organic', 'semi-detached', 'embedded', 'auto'], default='auto', help='Tipo de modelo COCOMO a utilizar (por defecto: auto)') - parser.add_argument('--cost', type=float, default=5000.0, - help='Costo por persona-mes en USD (por defecto: 5000)') + parser.add_argument('--cost', type=float, + help='Costo por persona-mes en USD (por defecto: tarifa fullstack × 176)') parser.add_argument('--ignore', action='append', help='Patrones adicionales de archivos a ignorar') parser.add_argument('--ext', action='append', @@ -295,6 +330,25 @@ if __name__ == "__main__": print(f"ERROR: El directorio del proyecto no existe: {args.project}") sys.exit(1) + # Verificar que el directorio de tarifas existe + if not RATES_DIR.exists(): + logger.warning(f"El directorio de tarifas no existe: {RATES_DIR}") + logger.warning("Se creará el directorio y se usarán valores predeterminados.") + try: + os.makedirs(RATES_DIR, exist_ok=True) + except Exception as e: + logger.error(f"Error al crear el directorio de tarifas: {e}") + + # Obtener costo por hora desde fullstack.rate y convertir a costo por persona-mes + if args.cost is None: + # Obtener la tarifa por hora y multiplicar por las horas mensuales + hourly_rate = get_fullstack_rate() + cost_per_pm = hourly_rate * HOURS_PER_MONTH + logger.info(f"Costo por hora: ${hourly_rate:.2f} × {HOURS_PER_MONTH} horas = ${cost_per_pm:.2f} por persona-mes") + else: + cost_per_pm = args.cost + logger.info(f"Usando costo por persona-mes especificado: ${cost_per_pm:.2f}") + # Preparar extensiones y patrones de ignorar extensions = DEFAULT_EXTENSIONS.copy() if args.ext: @@ -316,7 +370,7 @@ if __name__ == "__main__": model_type = args.model # Calcular estimaciones COCOMO - results = estimate_cocomo(loc, model_type, args.cost) + results = estimate_cocomo(loc, model_type, cost_per_pm) # Añadir ruta del proyecto para la presentación results['project_path'] = str(project_path)