import pandas as pd import os import PyLibrary.funciones_comunes as fc from translation_config import TranslationConfig import langid from openpyxl import load_workbook from openpyxl.styles import PatternFill, Alignment, Font from collections import defaultdict from openai_api_key import openai_api_key # Definir el logger a nivel de módulo logger = None def configurar_detector_idiomas(): codigos_idioma = [code.split("-")[0] for _, code in fc.IDIOMAS.values()] langid.set_languages(codigos_idioma) def detectar_idioma(texto, tipo_PLC): texto_limpio = fc.limpiar_texto(tipo_PLC, texto) if len(texto_limpio.strip()) < 3: # No detectar idioma en textos muy cortos return "unknown" try: idioma, _ = langid.classify(texto_limpio) return idioma except: return "unknown" def obtener_nombre_idioma(codigo_corto): for nombre, codigo in fc.IDIOMAS.values(): if codigo.startswith(codigo_corto): return nombre return "Desconocido" def exportar_para_traduccion(config: TranslationConfig): """ Exporta los textos del archivo maestro para su traducción, realizando validaciones y cálculos de afinidad en lotes. Los textos idénticos reciben afinidad 1 automáticamente. """ master_path = config.get_master_path() if not os.path.exists(master_path): print("El archivo maestro no existe.") return configurar_detector_idiomas() df_maestro = fc.read_dataframe_with_cleanup_retries(master_path) # Preparar DataFrame de exportación df_export = pd.DataFrame() primera_columna = df_maestro.columns[0] df_export[primera_columna] = df_maestro[primera_columna] columna_propuesta = f"{config.codigo_idioma_seleccionado}_Propuesto" df_export[columna_propuesta] = df_maestro[config.codigo_idioma_seleccionado] # Agregar columnas de validación si los idiomas son diferentes if config.codigo_columna_maestra != config.codigo_idioma_seleccionado: df_export["Validation_Error"] = "" df_export["Affinity_Score"] = None df_export["Idioma_Detectado"] = "" # Agregar columna del idioma secundario if config.codigo_idioma_secundario in df_maestro.columns: df_export[config.codigo_idioma_secundario] = df_maestro[config.codigo_idioma_secundario] ruta_export = config.get_translate_path() with pd.ExcelWriter(ruta_export, engine="openpyxl") as writer: df_export.to_excel(writer, index=False, sheet_name="Sheet1") workbook = writer.book worksheet = writer.sheets["Sheet1"] # Inmovilizar paneles en A2 worksheet.freeze_panes = "A2" # Configurar estilos wrap_alignment = Alignment(wrap_text=True, vertical="top") red_fill = PatternFill(start_color="FF0000", end_color="FF0000", fill_type="solid") yellow_fill = PatternFill(start_color="FFFF00", end_color="FFFF00", fill_type="solid") blue_fill = PatternFill(start_color="ADD8E6", end_color="ADD8E6", fill_type="solid") # Ajustar anchos de columna for col in worksheet.columns: max_length = 0 column = col[0].column_letter for cell in col: try: if cell.value: text_length = len(str(cell.value)) if text_length > 50: cell.alignment = wrap_alignment text_length = min(50, max(len(word) for word in str(cell.value).split())) max_length = max(max_length, text_length) except: pass adjusted_width = min(50, max_length + 2) worksheet.column_dimensions[column].width = adjusted_width if adjusted_width > 8 else 8 # Primera fase: Procesar detección de idioma y recopilar textos para afinidad texts_to_check = {} # Para textos que necesitan cálculo de afinidad identical_texts = {} # Para textos idénticos (afinidad 1) texto_a_filas = defaultdict(list) inconsistencias = 0 afinidad_baja = 0 progress_bar = fc.ProgressBar( worksheet.max_row - 1, prefix="Procesando textos:", suffix="Completado" ) for row in range(2, worksheet.max_row + 1): texto_original = worksheet.cell(row=row, column=1).value texto_propuesto = worksheet.cell(row=row, column=2).value if texto_original and texto_propuesto: # Detección de idioma texto_limpio = fc.limpiar_texto(config.codigo_tipo_PLC, texto_propuesto) if texto_propuesto == texto_limpio: texto_a_filas[texto_propuesto].append(row) idioma_detectado = detectar_idioma(texto_propuesto, config.codigo_tipo_PLC) idioma_esperado = fc.idiomas_shortcodefromcode(config.codigo_idioma_seleccionado) if idioma_detectado != "unknown" and idioma_detectado != idioma_esperado: worksheet.cell(row=row, column=2).fill = blue_fill nombre_idioma = obtener_nombre_idioma(idioma_detectado) worksheet.cell( row=row, column=df_export.columns.get_loc("Idioma_Detectado") + 1 ).value = nombre_idioma # Recopilar textos para afinidad si los idiomas son diferentes if config.codigo_columna_maestra != config.codigo_idioma_seleccionado: if pd.notnull(texto_propuesto) and texto_propuesto.strip() != "": # Compactar los textos para comparación texto_original_comp = fc.compactar_celda_traducida(config.codigo_tipo_PLC, str(texto_original)) texto_propuesto_comp = fc.compactar_celda_traducida(config.codigo_tipo_PLC, str(texto_propuesto)) # Si los textos son idénticos después de compactar, afinidad automática de 1 if texto_original_comp == texto_propuesto_comp: identical_texts[texto_original] = row else: texts_to_check[texto_original] = texto_propuesto progress_bar.increment() progress_bar.finish() # Configurar el modelo a usar modelo_llm = fc.LLM_MODELS["OpenAI"] # o el que se prefiera api_key = openai_api_key() # solo necesario para OpenAI y Grok # Segunda fase: Procesar textos idénticos y calcular afinidades en lote if config.codigo_columna_maestra != config.codigo_idioma_seleccionado: # Asignar afinidad 1 a textos idénticos logger.info(f"Asignando afinidad 1 a {len(identical_texts)} textos idénticos") for _, row in identical_texts.items(): col_idx = df_export.columns.get_loc("Affinity_Score") + 1 worksheet.cell(row=row, column=col_idx).value = 1.0 # Calcular afinidades para textos diferentes if texts_to_check: logger.info(f"Calculando afinidad para {len(texts_to_check)} textos diferentes") try: # Calcular afinidades affinities = fc.calcular_afinidad_batch( texts_to_check, config.codigo_tipo_PLC, modelo_llm, logger, api_key ) # Aplicar resultados de afinidad progress_bar = fc.ProgressBar( len(affinities), prefix="Aplicando afinidades:", suffix="Completado" ) for texto_original, afinidad in affinities.items(): row = next(row for row in range(2, worksheet.max_row + 1) if worksheet.cell(row=row, column=1).value == texto_original) col_idx = df_export.columns.get_loc("Affinity_Score") + 1 worksheet.cell(row=row, column=col_idx).value = afinidad if afinidad < 1: worksheet.cell(row=row, column=2).fill = yellow_fill afinidad_baja += 1 progress_bar.increment() progress_bar.finish() except Exception as e: logger.error(f"Error en el cálculo de afinidad por lotes: {str(e)}") print(f"Error en el cálculo de afinidad por lotes: {str(e)}") # Marcar celdas duplicadas bold_font = Font(bold=True) celdas_duplicadas = 0 for filas in texto_a_filas.values(): if len(filas) > 1: for row in filas: cell = worksheet.cell(row=row, column=2) cell.font = bold_font celdas_duplicadas += len(filas) # Imprimir resumen print(f"\nArchivo exportado para traducción: {ruta_export}") print("Las celdas con idioma incorrecto han sido marcadas en azul.") print("Se ha añadido el nombre del idioma detectado cuando es diferente del esperado.") print(f"Se ha agregado la columna del idioma secundario ({config.codigo_idioma_secundario}) al final de la planilla.") if config.codigo_columna_maestra != config.codigo_idioma_seleccionado: print(f"Se encontraron {len(identical_texts)} textos idénticos (afinidad 1)") print(f"Se encontraron {inconsistencias} celdas con errores de validación (marcadas en rojo)") print(f"Se encontraron {afinidad_baja} celdas con afinidad menor a 1 (marcadas en amarillo)") print(f"Se han marcado {celdas_duplicadas} celdas en negrita por tener texto duplicado.") def run(config: TranslationConfig): global logger logger = fc.configurar_logger(config.work_dir) script_name = os.path.basename(__file__) print(f"\rIniciando: {script_name}\r") exportar_para_traduccion(config) if __name__ == "__main__": import menu_pasos_traduccion menu_pasos_traduccion.main()