Saltar a contenido

Importación dinámica

El autoinforme es un script genérico que puede procesar cualquier campaña. La configuración específica de cada proyecto (rutas, parámetros, profundidades, nombre de empresa) vive en un módulo Python separado que se carga en tiempo de ejecución según el nombre del proyecto. Esto elimina la necesidad de editar el script central cada vez que se agrega una nueva campaña.

El problema: configuración por proyecto

Sin importación dinámica, el script tendría bloques if/elif:

# MAL: requiere modificar el script central para cada proyecto
if proyecto == 'los_vilos':
    from configs import los_vilos as cfg
elif proyecto == 'coquimbo':
    from configs import coquimbo as cfg
elif proyecto == 'iquique':
    from configs import iquique as cfg
# ... y así indefinidamente

Con importación dinámica, el script central no cambia nunca:

# BIEN: el script carga automáticamente el módulo correcto
import importlib
cfg = importlib.import_module(f'configs.{nombre_proyecto}')

importlib.import_module

import importlib

def cargar_config(nombre_proyecto):
    """
    Carga el módulo de configuración para el proyecto indicado.
    Lanza ImportError con mensaje claro si no existe.
    """
    nombre_modulo = f'configs.{nombre_proyecto}'
    try:
        modulo = importlib.import_module(nombre_modulo)
    except ModuleNotFoundError:
        raise FileNotFoundError(
            f"No existe configuración para '{nombre_proyecto}'. "
            f"Crear el archivo configs/{nombre_proyecto}.py"
        )
    return modulo

# Uso
cfg = cargar_config('los_vilos_oct2025')

ruta_datos = cfg.RUTA_DATOS
profundidades = cfg.PROFUNDIDADES
empresa = cfg.EMPRESA

Estructura de un módulo de configuración

Cada proyecto tiene un archivo en configs/:

autoinforme/
  configs/
    __init__.py          ← vacío, hace de configs un paquete
    los_vilos_oct2025.py
    coquimbo_mar2025.py
    iquique_ene2026.py
  autoinforme.py         ← script central (nunca se modifica)

Un módulo de configuración típico:

# configs/los_vilos_oct2025.py
from pathlib import Path

PROYECTO     = 'Los Vilos — Campaña octubre 2025'
EMPRESA      = 'Puerto Los Vilos S.A.'
FECHA_INICIO = '2025-10-01'
FECHA_FIN    = '2025-10-31'

RUTA_BASE    = Path('/mnt/c/Users/Usuario/proyectos/los_vilos_2025')
RUTA_DATOS   = RUTA_BASE / 'datos' / 'corrientes_procesadas.xlsx'
RUTA_FIGURAS = RUTA_BASE / 'figuras_magnitud'
RUTA_SALIDA  = RUTA_BASE / 'informe' / 'Los_Vilos_Corrientes_Oct2025.docx'
PLANTILLA    = Path('plantillas') / 'corrientes_v3.docx'

PROFUNDIDADES = [3, 5, 7, 9, 11, 13, 15]
BINS_VEL      = [0, 0.05, 0.1, 0.2, float('inf')]

Verificar que el módulo tiene los atributos necesarios

ATRIBUTOS_REQUERIDOS = [
    'PROYECTO', 'EMPRESA', 'FECHA_INICIO', 'FECHA_FIN',
    'RUTA_DATOS', 'RUTA_FIGURAS', 'RUTA_SALIDA', 'PLANTILLA',
    'PROFUNDIDADES',
]

def validar_config(cfg, nombre_proyecto):
    faltantes = [attr for attr in ATRIBUTOS_REQUERIDOS if not hasattr(cfg, attr)]
    if faltantes:
        raise AttributeError(
            f"Configuración '{nombre_proyecto}' incompleta. "
            f"Faltan: {faltantes}"
        )

Recargar un módulo modificado

Durante el desarrollo, si se edita el archivo de configuración con el script ya corriendo en Spyder, hay que recargar el módulo para ver los cambios:

import importlib

cfg = importlib.import_module('configs.los_vilos_oct2025')

# Después de editar el archivo:
importlib.reload(cfg)

Listar proyectos disponibles

from pathlib import Path

def listar_proyectos(carpeta_configs='configs'):
    carpeta = Path(carpeta_configs)
    proyectos = [
        p.stem for p in carpeta.glob('*.py')
        if p.stem != '__init__'
    ]
    return sorted(proyectos)

print("Proyectos disponibles:")
for p in listar_proyectos():
    print(f"  {p}")

Patrón del script central

El script autoinforme.py recibe el nombre del proyecto por argumento de línea de comandos o por input interactivo:

import sys
import importlib

def main():
    if len(sys.argv) > 1:
        nombre_proyecto = sys.argv[1]
    else:
        proyectos = listar_proyectos()
        print("Proyectos disponibles:")
        for i, p in enumerate(proyectos):
            print(f"  [{i}] {p}")
        idx = int(input("Seleccionar: "))
        nombre_proyecto = proyectos[idx]

    cfg = cargar_config(nombre_proyecto)
    validar_config(cfg, nombre_proyecto)

    print(f"\nProcesando: {cfg.PROYECTO}")

    # El resto del pipeline usa cfg.RUTA_DATOS, cfg.PROFUNDIDADES, etc.
    datos = cargar_datos(cfg.RUTA_DATOS)
    stats = calcular_estadisticas(datos, cfg.PROFUNDIDADES)
    generar_figuras(datos, stats, cfg.RUTA_FIGURAS)
    generar_informe(cfg.PLANTILLA, cfg.RUTA_SALIDA, cfg, stats)

if __name__ == '__main__':
    main()

Alternativa con YAML

Si la configuración es puramente datos (sin expresiones Python ni herencia entre proyectos), se puede guardar como YAML y cargar con yaml.safe_load (ver capítulo 07). La ventaja del módulo .py es que permite expresiones, lógica condicional y paths calculados con Path(__file__).parent. La ventaja del YAML es que cualquiera puede editarlo sin saber Python.