diff --git a/bin/cocomo.py b/bin/cocomo.py index ef48937..503bb3a 100755 --- a/bin/cocomo.py +++ b/bin/cocomo.py @@ -257,21 +257,70 @@ def determine_cocomo_model(loc): else: return 'embedded' # Proyectos grandes (más de 300K líneas) -def get_fullstack_rate(): +def get_available_programmer_types(): """ - 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. + Obtiene una lista de los tipos de programadores disponibles + basados en los archivos .rate existentes en el directorio de tarifas. + + Returns: + List: Lista de tipos de programadores disponibles + """ + if not RATES_DIR.exists(): + logger.warning(f"El directorio de tarifas no existe: {RATES_DIR}") + return ['fullstack'] # Por defecto solo fullstack si no hay directorio + + try: + # Buscar archivos .rate en el directorio + rate_files = list(RATES_DIR.glob('*.rate')) + + # Extraer nombres de programadores de los nombres de archivo + programmer_types = [file.stem for file in rate_files] + + if not programmer_types: + logger.warning("No se encontraron archivos de tarifas. Usando solo 'fullstack' por defecto.") + return ['fullstack'] + + return sorted(programmer_types) + except Exception as e: + logger.error(f"Error al buscar tipos de programadores disponibles: {e}") + return ['fullstack'] # En caso de error, devolver tipo por defecto + +def get_programmer_rate(programmer_type='fullstack'): + """ + Obtiene la tarifa por hora de un tipo específico de programador desde el archivo de configuración. + Si el archivo no existe, devuelve un valor predeterminado según el tipo. + + Args: + programmer_type: Tipo de programador (por defecto: 'fullstack') Returns: Float: Tarifa por hora en USD """ - rate_file = RATES_DIR / "fullstack.rate" + rate_file = RATES_DIR / f"{programmer_type}.rate" + + # Valores predeterminados por tipo de programador + default_rates = { + 'fullstack': 45.00, + 'frontend': 40.00, + 'backend': 42.00, + 'devops': 50.00, + 'python': 35.00, + 'java': 40.00, + 'php': 30.00, + 'mobile': 45.00, + 'data': 55.00, + 'ml': 65.00, + 'cloud': 60.00, + 'bash': 20.00 + } + + default_rate = default_rates.get(programmer_type, 40.00) # 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 + logger.warning(f"Usando tarifa predeterminada de {default_rate:.2f} USD/hora para {programmer_type}") + return default_rate # Leer la tarifa del archivo try: @@ -281,8 +330,8 @@ def get_fullstack_rate(): 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 + logger.warning(f"Usando tarifa predeterminada de {default_rate:.2f} USD/hora para {programmer_type}") + return default_rate def print_results(results): """ @@ -307,13 +356,19 @@ def print_results(results): print("=====================================================\n") if __name__ == "__main__": + # Obtener los tipos de programadores disponibles para el parámetro --type + available_types = get_available_programmer_types() + available_types_str = ', '.join(available_types) + # Parámetros de línea de comandos parser = argparse.ArgumentParser(description='Calculadora COCOMO para estimar costos de proyectos de software') 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('--type', choices=available_types, default='fullstack', + help=f'Tipo de programador para calcular costos (por defecto: fullstack). Disponibles: {available_types_str}') parser.add_argument('--cost', type=float, - help='Costo por persona-mes en USD (por defecto: tarifa fullstack × 176)') + help='Costo por persona-mes en USD (por defecto: tarifa del tipo de programador × 176)') parser.add_argument('--ignore', action='append', help='Patrones adicionales de archivos a ignorar') parser.add_argument('--ext', action='append', @@ -348,16 +403,18 @@ if __name__ == "__main__": 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 - programmer_type = 'fullstack' # Por defecto usamos la tarifa de fullstack + # Obtener tipo de programador y su tarifa + programmer_type = args.type # Usar el tipo especificado por el usuario + + # Obtener costo por hora desde el archivo .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() + hourly_rate = get_programmer_rate(programmer_type) cost_per_pm = hourly_rate * HOURS_PER_MONTH logger.info(f"Costo por hora ({programmer_type}): ${hourly_rate:.2f} × {HOURS_PER_MONTH} horas = ${cost_per_pm:.2f} por persona-mes") else: cost_per_pm = args.cost - programmer_type = 'personalizado' # Si se especifica manualmente + programmer_type = f"{programmer_type} (personalizado)" # Indicar que es un valor personalizado hourly_rate = cost_per_pm / HOURS_PER_MONTH logger.info(f"Usando costo por persona-mes especificado: ${cost_per_pm:.2f} (${hourly_rate:.2f}/hora)")