Implementación de nuevas funcionalidades en la API y mejoras en la interfaz de usuario

- Se añadió una nueva ruta API para abrir la carpeta de un grupo en el explorador de archivos, mejorando la accesibilidad.
- Se implementaron botones en la interfaz para abrir la carpeta del grupo y copiar la ruta del grupo al portapapeles.
- Se mejoró la lógica de visualización de botones en la interfaz, permitiendo un mejor manejo de la visibilidad de los mismos.
- Se realizaron ajustes en los logs de ejecución para reflejar las nuevas funcionalidades y cambios en los directorios de trabajo.
This commit is contained in:
Miguel 2025-06-12 20:06:45 +02:00
parent 9ac769e2fc
commit be3b333491
31 changed files with 9812 additions and 29078 deletions

101
app.py
View File

@ -913,6 +913,107 @@ def open_group_in_editor(editor, group_system, group_id):
"message": f"Error al abrir {editor}: {str(e)}" "message": f"Error al abrir {editor}: {str(e)}"
}), 500 }), 500
@app.route("/api/open-group-folder/<group_system>/<group_id>", methods=["POST"])
def open_group_folder(group_system, group_id):
"""Abrir carpeta de un grupo en el explorador de archivos"""
try:
# Determinar directorio según el sistema
if group_system == 'config':
script_group_path = os.path.join(config_manager.script_groups_path, group_id)
if not os.path.isdir(script_group_path):
return jsonify({
"status": "error",
"message": f"Directorio del grupo config '{group_id}' no encontrado"
}), 404
elif group_system == 'launcher':
group = launcher_manager.get_launcher_group(group_id)
if not group:
return jsonify({
"status": "error",
"message": f"Grupo launcher '{group_id}' no encontrado"
}), 404
script_group_path = group["directory"]
if not os.path.isdir(script_group_path):
return jsonify({
"status": "error",
"message": f"Directorio del grupo launcher '{group['name']}' no encontrado"
}), 404
else:
return jsonify({
"status": "error",
"message": f"Sistema de grupo '{group_system}' no válido. Usar 'config' o 'launcher'"
}), 400
# Abrir en el explorador según el sistema operativo
try:
if sys.platform == "win32":
os.startfile(script_group_path)
elif sys.platform == "darwin": # macOS
subprocess.Popen(["open", script_group_path])
else: # linux variants
subprocess.Popen(["xdg-open", script_group_path])
return jsonify({
"status": "success",
"message": f"Abriendo '{script_group_path}' en el explorador",
"path": script_group_path
})
except Exception as e:
return jsonify({
"status": "error",
"message": f"Error al abrir el explorador en '{script_group_path}': {str(e)}"
}), 500
except Exception as e:
print(f"Error opening folder for {group_system} group '{group_id}': {str(e)}")
return jsonify({
"status": "error",
"message": f"Error al abrir carpeta: {str(e)}"
}), 500
@app.route("/api/get-group-path/<group_system>/<group_id>", methods=["GET"])
def get_group_path(group_system, group_id):
"""Obtener el path completo de un grupo de scripts"""
try:
# Determinar directorio según el sistema
if group_system == 'config':
script_group_path = os.path.join(config_manager.script_groups_path, group_id)
if not os.path.isdir(script_group_path):
return jsonify({
"status": "error",
"message": f"Directorio del grupo config '{group_id}' no encontrado"
}), 404
elif group_system == 'launcher':
group = launcher_manager.get_launcher_group(group_id)
if not group:
return jsonify({
"status": "error",
"message": f"Grupo launcher '{group_id}' no encontrado"
}), 404
script_group_path = group["directory"]
if not os.path.isdir(script_group_path):
return jsonify({
"status": "error",
"message": f"Directorio del grupo launcher '{group['name']}' no encontrado"
}), 404
else:
return jsonify({
"status": "error",
"message": f"Sistema de grupo '{group_system}' no válido. Usar 'config' o 'launcher'"
}), 400
return jsonify({
"status": "success",
"path": script_group_path
})
except Exception as e:
print(f"Error getting path for {group_system} group '{group_id}': {str(e)}")
return jsonify({
"status": "error",
"message": f"Error al obtener path: {str(e)}"
}), 500
if __name__ == "__main__": if __name__ == "__main__":
# --- Start Flask in a background thread --- # --- Start Flask in a background thread ---
flask_thread = threading.Thread(target=run_flask, daemon=True) flask_thread = threading.Thread(target=run_flask, daemon=True)

View File

@ -1,39 +1,35 @@
--- Log de Ejecución: x1.py --- --- Log de Ejecución: x1.py ---
Grupo: EmailCrono Grupo: EmailCrono
Directorio de Trabajo: C:\Trabajo\SIDEL\12 - SAE052 - Syrup Update & GSD Update\Reporte\Emails Directorio de Trabajo: C:\Trabajo\SIDEL\14 - E5.007172 - Modifica O&U - SAE340\Reporte\Email
Inicio: 2025-05-18 16:00:44 Inicio: 2025-06-09 17:06:35
Fin: 2025-05-18 16:00:44 Fin: 2025-06-09 17:06:36
Duración: 0:00:00.445734 Duración: 0:00:00.370858
Estado: SUCCESS (Código de Salida: 0) Estado: SUCCESS (Código de Salida: 0)
--- SALIDA ESTÁNDAR (STDOUT) --- --- SALIDA ESTÁNDAR (STDOUT) ---
Working directory: C:\Trabajo\SIDEL\12 - SAE052 - Syrup Update & GSD Update\Reporte\Emails Working directory: C:\Trabajo\SIDEL\14 - E5.007172 - Modifica O&U - SAE340\Reporte\Email
Input directory: C:\Trabajo\SIDEL\12 - SAE052 - Syrup Update & GSD Update\Reporte\Emails Input directory: C:\Trabajo\SIDEL\14 - E5.007172 - Modifica O&U - SAE340\Reporte\Email
Output directory: C:/Users/migue/OneDrive/Miguel/Obsidean/Trabajo/VM/04-SIDEL/12 - SAE052 - Syrup Update & GSD Update Output directory: C:/Users/migue/OneDrive/Miguel/Obsidean/Trabajo/VM/04-SIDEL/14 - E5.007172 - Modifica O&U - SAE340
Cronologia file: C:/Users/migue/OneDrive/Miguel/Obsidean/Trabajo/VM/04-SIDEL/12 - SAE052 - Syrup Update & GSD Update\cronologia.md Cronologia file: C:/Users/migue/OneDrive/Miguel/Obsidean/Trabajo/VM/04-SIDEL/14 - E5.007172 - Modifica O&U - SAE340\cronologia.md
Attachments directory: C:\Trabajo\SIDEL\12 - SAE052 - Syrup Update & GSD Update\Reporte\Emails\adjuntos Attachments directory: C:\Trabajo\SIDEL\14 - E5.007172 - Modifica O&U - SAE340\Reporte\Email\adjuntos
Beautify rules file: D:\Proyectos\Scripts\ParamManagerScripts\backend\script_groups\EmailCrono\config\beautify_rules.json Beautify rules file: D:\Proyectos\Scripts\ParamManagerScripts\backend\script_groups\EmailCrono\config\beautify_rules.json
Found 2 .eml files Found 2 .eml files
Loaded 0 existing messages Loaded 0 existing messages
Processing C:\Trabajo\SIDEL\12 - SAE052 - Syrup Update & GSD Update\Reporte\Emails\I_ Backup SAE052.eml Processing C:\Trabajo\SIDEL\14 - E5.007172 - Modifica O&U - SAE340\Reporte\Email\E5.007172.eml
Aplicando reglas de prioridad 1 Aplicando reglas de prioridad 1
Aplicando reglas de prioridad 2 Aplicando reglas de prioridad 2
Aplicando reglas de prioridad 3 Aplicando reglas de prioridad 3
Aplicando reglas de prioridad 4 Aplicando reglas de prioridad 4
Processing C:\Trabajo\SIDEL\12 - SAE052 - Syrup Update & GSD Update\Reporte\Emails\Parametri Modificati SAE052.eml Processing C:\Trabajo\SIDEL\14 - E5.007172 - Modifica O&U - SAE340\Reporte\Email\R_ NOTICE OF GOODS READY ASSIGN ORDER 169423 - Won Opportunity - Services- 169423.A.2.2 - AJETHAI CO., LTD. - Thailand - Filling - Service CRM_0037299==CHECK PAYMENT TERM==E5.007172.eml
Aplicando reglas de prioridad 1
Aplicando reglas de prioridad 2
Aplicando reglas de prioridad 3
Aplicando reglas de prioridad 4
Estadísticas de procesamiento: Estadísticas de procesamiento:
- Total mensajes encontrados: 2 - Total mensajes encontrados: 1
- Mensajes únicos añadidos: 2 - Mensajes únicos añadidos: 1
- Mensajes duplicados ignorados: 0 - Mensajes duplicados ignorados: 0
Writing 2 messages to C:/Users/migue/OneDrive/Miguel/Obsidean/Trabajo/VM/04-SIDEL/12 - SAE052 - Syrup Update & GSD Update\cronologia.md Writing 1 messages to C:/Users/migue/OneDrive/Miguel/Obsidean/Trabajo/VM/04-SIDEL/14 - E5.007172 - Modifica O&U - SAE340\cronologia.md
--- ERRORES (STDERR) --- --- ERRORES (STDERR) ---
Ninguno Ninguno

View File

@ -8,7 +8,7 @@
"cronologia_file": "cronologia.md" "cronologia_file": "cronologia.md"
}, },
"level3": { "level3": {
"output_directory": "C:/Users/migue/OneDrive/Miguel/Obsidean/Trabajo/VM/04-SIDEL/12 - SAE052 - Syrup Update & GSD Update" "output_directory": "C:/Users/migue/OneDrive/Miguel/Obsidean/Trabajo/VM/04-SIDEL/14 - E5.007172 - Modifica O&U - SAE340"
}, },
"working_directory": "C:\\Trabajo\\SIDEL\\12 - SAE052 - Syrup Update & GSD Update\\Reporte\\Emails" "working_directory": "C:\\Trabajo\\SIDEL\\14 - E5.007172 - Modifica O&U - SAE340\\Reporte\\Email"
} }

View File

@ -1,6 +1,7 @@
{ {
"path": "C:\\Trabajo\\SIDEL\\12 - SAE052 - Syrup Update & GSD Update\\Reporte\\Emails", "path": "C:\\Trabajo\\SIDEL\\14 - E5.007172 - Modifica O&U - SAE340\\Reporte\\Email",
"history": [ "history": [
"C:\\Trabajo\\SIDEL\\14 - E5.007172 - Modifica O&U - SAE340\\Reporte\\Email",
"C:\\Trabajo\\SIDEL\\12 - SAE052 - Syrup Update & GSD Update\\Reporte\\Emails", "C:\\Trabajo\\SIDEL\\12 - SAE052 - Syrup Update & GSD Update\\Reporte\\Emails",
"C:\\Trabajo\\SIDEL\\10 - E5.007095 - Modifica O&U - SAE463\\Reporte\\Email", "C:\\Trabajo\\SIDEL\\10 - E5.007095 - Modifica O&U - SAE463\\Reporte\\Email",
"C:\\Trabajo\\SIDEL\\08 - Masselli TEST\\Reporte\\EMAILs", "C:\\Trabajo\\SIDEL\\08 - Masselli TEST\\Reporte\\EMAILs",
@ -9,7 +10,6 @@
"C:\\Trabajo\\VM\\40 - 93040 - HENKEL - NEXT2 Problem\\Reporte\\EmailTody", "C:\\Trabajo\\VM\\40 - 93040 - HENKEL - NEXT2 Problem\\Reporte\\EmailTody",
"C:\\Trabajo\\VM\\30 - 9.3941- Kosme - Portogallo (Modifica + Linea)\\Reporte\\Emails", "C:\\Trabajo\\VM\\30 - 9.3941- Kosme - Portogallo (Modifica + Linea)\\Reporte\\Emails",
"C:\\Users\\migue\\OneDrive\\Miguel\\Obsidean\\Trabajo\\VM\\30 - 9.3941- Kosme - Portogallo (Modifica + Linea)\\Emails", "C:\\Users\\migue\\OneDrive\\Miguel\\Obsidean\\Trabajo\\VM\\30 - 9.3941- Kosme - Portogallo (Modifica + Linea)\\Emails",
"C:\\Trabajo\\VM\\40 - 93040 - HENKEL - NEXT2 Problem\\Reporte\\Emails\\Trial", "C:\\Trabajo\\VM\\40 - 93040 - HENKEL - NEXT2 Problem\\Reporte\\Emails\\Trial"
"C:\\Trabajo\\VM\\40 - 93040 - HENKEL - NEXT2 Problem\\Reporte\\Emails"
] ]
} }

File diff suppressed because it is too large Load Diff

View File

@ -2,49 +2,10 @@
| Network | Type | Address | Device Name | Sub-Device | OrderNo | Type | IO Type | IO Address | Number of Bits | | Network | Type | Address | Device Name | Sub-Device | OrderNo | Type | IO Type | IO Address | Number of Bits |
|-----|-----|-----|-----|-----|-----|-----|-----|-----|-----| |-----|-----|-----|-----|-----|-----|-----|-----|-----|-----|
| PN/IE_1 | Ethernet/Profinet | 10.1.30.31 | U30110-AxisX | DO SERVO_1 | N/A | DO SERVO | Input | `EW 100..119` | 160 | | PLC Local Modules | Local I/O | Local | PLC A40510 | A41110 | 6ES7 131-6BF01-0BA0 | DI 8x24VDC ST | Input | `EW 0..0` | 8 |
| PN/IE_1 | Ethernet/Profinet | 10.1.30.31 | U30110-AxisX | DO SERVO_1 | N/A | DO SERVO | Output | `AW 100..119` | 160 | | PLC Local Modules | Local I/O | Local | PLC A40510 | A41111 | 6ES7 131-6BF01-0BA0 | DI 8x24VDC ST | Input | `EW 1..1` | 8 |
| PN/IE_1 | Ethernet/Profinet | 10.1.30.32 | U30210-AxisY | DO SERVO_1 | N/A | DO SERVO | Input | `EW 120..139` | 160 | | PLC Local Modules | Local I/O | Local | PLC A40510 | A41120 | 6ES7 131-6BF01-0BA0 | DI 8x24VDC ST | Input | `EW 2..2` | 8 |
| PN/IE_1 | Ethernet/Profinet | 10.1.30.32 | U30210-AxisY | DO SERVO_1 | N/A | DO SERVO | Output | `AW 120..139` | 160 | | PLC Local Modules | Local I/O | Local | PLC A40510 | A41121 | 6ES7 131-6BF01-0BA0 | DI 8x24VDC ST | Input | `EW 3..3` | 8 |
| PN/IE_1 | Ethernet/Profinet | 10.1.30.33 | U30310 | DO with manufacturer telegr. 102_1 | N/A | DO with manufacturer telegr. 102 | Input | `EW 4..51` | 384 | | PLC Local Modules | Local I/O | Local | PLC A40510 | A41130 | 6ES7 132-6BF01-0BA0 | DQ 8x24VDC/0.5A ST | Output | `AW 0..0` | 8 |
| PN/IE_1 | Ethernet/Profinet | 10.1.30.33 | U30310 | DO with manufacturer telegr. 102_1 | N/A | DO with manufacturer telegr. 102 | Output | `AW 3..50` | 384 | | PLC Local Modules | Local I/O | Local | PLC A40510 | A41131 | 6ES7 132-6BF01-0BA0 | DQ 8x24VDC/0.5A ST | Output | `AW 1..1` | 8 |
| PN/IE_1 | Ethernet/Profinet | 10.1.30.34 | U30410 | DO with manufacturer telegr. 102_1 | N/A | DO with manufacturer telegr. 102 | Input | `EW 52..99` | 384 | | PLC Local Modules | Local I/O | Local | PLC A40510 | A41140 | 6ES7 132-6BF01-0BA0 | DQ 8x24VDC/0.5A ST | Output | `AW 2..2` | 8 |
| PN/IE_1 | Ethernet/Profinet | 10.1.30.34 | U30410 | DO with manufacturer telegr. 102_1 | N/A | DO with manufacturer telegr. 102 | Output | `AW 51..98` | 384 |
| PN/IE_1 | Ethernet/Profinet | 10.1.30.35 | U30510 | DO with manufacturer telegr. 102_1 | N/A | DO with manufacturer telegr. 102 | Input | `EW 156..203` | 384 |
| PN/IE_1 | Ethernet/Profinet | 10.1.30.35 | U30510 | DO with manufacturer telegr. 102_1 | N/A | DO with manufacturer telegr. 102 | Output | `AW 144..191` | 384 |
| PN/IE_1 | Ethernet/Profinet | 10.1.30.36 | U30610 | DO with manufacturer telegr. 102_1 | N/A | DO with manufacturer telegr. 102 | Input | `EW 204..251` | 384 |
| PN/IE_1 | Ethernet/Profinet | 10.1.30.36 | U30610 | DO with manufacturer telegr. 102_1 | N/A | DO with manufacturer telegr. 102 | Output | `AW 192..239` | 384 |
| PN/IE_1 | Ethernet/Profinet | 10.1.30.37 | M30710 | DS402_Extend-A | N/A | N/A | Input | `EW 4066..4087` | 176 |
| PN/IE_1 | Ethernet/Profinet | 10.1.30.37 | M30710 | DS402_Extend-A | N/A | N/A | Output | `AW 4132..4175` | 352 |
| PN/IE_1 | Ethernet/Profinet | 10.1.30.40 | M31010 | Module I/O (08 words) | N/A | Module I/O (08 words) | Input | `EW 272..287` | 128 |
| PN/IE_1 | Ethernet/Profinet | 10.1.30.40 | M31010 | Module I/O (08 words) | N/A | Module I/O (08 words) | Output | `AW 272..287` | 128 |
| PN/IE_1 | Ethernet/Profinet | 10.1.30.41 | M31110 | DS402_Extend-A | N/A | N/A | Input | `EW 4000..4021` | 176 |
| PN/IE_1 | Ethernet/Profinet | 10.1.30.41 | M31110 | DS402_Extend-A | N/A | N/A | Output | `AW 4000..4043` | 352 |
| PN/IE_1 | Ethernet/Profinet | 10.1.30.42 | M31210 | DS402_Extend-A | N/A | N/A | Input | `EW 4022..4043` | 176 |
| PN/IE_1 | Ethernet/Profinet | 10.1.30.42 | M31210 | DS402_Extend-A | N/A | N/A | Output | `AW 4044..4087` | 352 |
| PN/IE_1 | Ethernet/Profinet | 10.1.30.43 | M31310 | DS402_Extend-A | N/A | N/A | Input | `EW 4044..4065` | 176 |
| PN/IE_1 | Ethernet/Profinet | 10.1.30.43 | M31310 | DS402_Extend-A | N/A | N/A | Output | `AW 4088..4131` | 352 |
| PN/IE_1 | Ethernet/Profinet | 10.1.30.44 | M31410 | Module I/O (08 words) | N/A | Module I/O (08 words) | Input | `EW 288..303` | 128 |
| PN/IE_1 | Ethernet/Profinet | 10.1.30.44 | M31410 | Module I/O (08 words) | N/A | Module I/O (08 words) | Output | `AW 288..303` | 128 |
| PN/IE_1 | Ethernet/Profinet | 10.1.30.45 | M31510 | Module I/O (08 words) | N/A | Module I/O (08 words) | Input | `EW 304..319` | 128 |
| PN/IE_1 | Ethernet/Profinet | 10.1.30.45 | M31510 | Module I/O (08 words) | N/A | Module I/O (08 words) | Output | `AW 304..319` | 128 |
| PN/IE_1 | Ethernet/Profinet | 10.1.30.46 | M31610 | DS402_Extend-A | N/A | N/A | Input | `EW 4088..4109` | 176 |
| PN/IE_1 | Ethernet/Profinet | 10.1.30.46 | M31610 | DS402_Extend-A | N/A | N/A | Output | `AW 4176..4219` | 352 |
| PN/IE_1 | Ethernet/Profinet | 10.1.30.47 | M31710 | DS402_Extend-A | N/A | N/A | Input | `EW 4110..4131` | 176 |
| PN/IE_1 | Ethernet/Profinet | 10.1.30.47 | M31710 | DS402_Extend-A | N/A | N/A | Output | `AW 4220..4263` | 352 |
| PN/IE_1 | Ethernet/Profinet | 10.1.30.48 | M31810 | DS402_Extend-A | N/A | N/A | Input | `EW 4132..4153` | 176 |
| PN/IE_1 | Ethernet/Profinet | 10.1.30.48 | M31810 | DS402_Extend-A | N/A | N/A | Output | `AW 4264..4307` | 352 |
| PN/IE_1 | Ethernet/Profinet | 10.1.30.49 | M31910 | DS402_Extend-A | N/A | N/A | Input | `EW 4154..4175` | 176 |
| PN/IE_1 | Ethernet/Profinet | 10.1.30.49 | M31910 | DS402_Extend-A | N/A | N/A | Output | `AW 4308..4351` | 352 |
| PN/IE_1 | Ethernet/Profinet | 10.1.30.70 | M34010 | DS402_Extend-A | N/A | N/A | Input | `EW 4176..4197` | 176 |
| PN/IE_1 | Ethernet/Profinet | 10.1.30.70 | M34010 | DS402_Extend-A | N/A | N/A | Output | `AW 4352..4395` | 352 |
| PN/IE_1 | Ethernet/Profinet | 10.1.30.71 | M34110 | DS402_Extend-A | N/A | N/A | Input | `EW 4198..4219` | 176 |
| PN/IE_1 | Ethernet/Profinet | 10.1.30.71 | M34110 | DS402_Extend-A | N/A | N/A | Output | `AW 4396..4439` | 352 |
| PN/IE_1 | Ethernet/Profinet | 10.1.30.72 | M34210 | DS402_Extend-A | N/A | N/A | Input | `EW 4220..4241` | 176 |
| PN/IE_1 | Ethernet/Profinet | 10.1.30.72 | M34210 | DS402_Extend-A | N/A | N/A | Output | `AW 4440..4483` | 352 |
| PN/IE_1 | Ethernet/Profinet | 10.1.30.73 | M34310 | DS402_Extend-A | N/A | N/A | Input | `EW 4242..4263` | 176 |
| PN/IE_1 | Ethernet/Profinet | 10.1.30.73 | M34310 | DS402_Extend-A | N/A | N/A | Output | `AW 4484..4527` | 352 |
| PN/IE_1 | Ethernet/Profinet | 10.1.30.74 | M34410 | Module I/O (08 words) | N/A | Module I/O (08 words) | Input | `EW 362..377` | 128 |
| PN/IE_1 | Ethernet/Profinet | 10.1.30.74 | M34410 | Module I/O (08 words) | N/A | Module I/O (08 words) | Output | `AW 340..355` | 128 |
| PN/IE_1 | Ethernet/Profinet | 10.1.30.170 | E44010-Encoder | EO Encoder Multiturn V2.x_1 | N/A | N/A | Input | `EW 140..155` | 128 |
| PN/IE_1 | Ethernet/Profinet | 10.1.30.170 | E44010-Encoder | EO Encoder Multiturn V2.x_1 | N/A | N/A | Output | `AW 140..143` | 32 |

View File

@ -1,13 +1,13 @@
--- Log de Ejecución: x2_process_CAx.py --- --- Log de Ejecución: x2_process_CAx.py ---
Grupo: IO_adaptation Grupo: IO_adaptation
Directorio de Trabajo: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia Directorio de Trabajo: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia
Inicio: 2025-06-07 14:29:10 Inicio: 2025-06-08 00:15:28
Fin: 2025-06-07 14:29:16 Fin: 2025-06-08 00:15:33
Duración: 0:00:06.140835 Duración: 0:00:04.525461
Estado: SUCCESS (Código de Salida: 0) Estado: SUCCESS (Código de Salida: 0)
--- SALIDA ESTÁNDAR (STDOUT) --- --- SALIDA ESTÁNDAR (STDOUT) ---
--- AML (CAx Export) to Hierarchical JSON and Obsidian MD Converter (v32.2 - Simplified IO Address Format (Separate Start/End)) --- --- AML (CAx Export) to Hierarchical JSON and Obsidian MD Converter (v32.4 - Fixed Excel Integer Format for IO Addresses) ---
Using configured working directory: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia Using configured working directory: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia
Using Working Directory for Output: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia Using Working Directory for Output: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia
Input AML: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01.aml Input AML: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01.aml
@ -17,45 +17,45 @@ Output IO Debug Tree MD: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\980
Processing AML file: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01.aml Processing AML file: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01.aml
Pass 1: Found 363 InternalElement(s). Populating device dictionary... Pass 1: Found 363 InternalElement(s). Populating device dictionary...
Pass 2: Identifying PLCs and Networks (Refined v2)... Pass 2: Identifying PLCs and Networks (Refined v2)...
Identified Network: PN/IE_1 (6ce86626-0043-4a58-b675-cc13ac87121c) Type: Ethernet/Profinet Identified Network: PN/IE_1 (f9f048af-beb4-4e14-8a03-dc27e564649c) Type: Ethernet/Profinet
Identified PLC: A40510 (fc0d3bac-267e-488a-8dcf-7dc8599d80e8) - Type: CPU 1514SP T-2 PN OrderNo: 6ES7 514-2VN03-0AB0 Identified PLC: A40510 (fc0d3bac-267e-488a-8dcf-7dc8599d80e8) - Type: CPU 1514SP T-2 PN OrderNo: 6ES7 514-2VN03-0AB0
Pass 3: Processing InternalLinks (Robust Network Mapping & IO)... Pass 3: Processing InternalLinks (Robust Network Mapping & IO)...
Found 103 InternalLink(s). Found 103 InternalLink(s).
Mapping Device/Node 'E1' (NodeID:ab796923-4471-4a60-98f4-f8ea5920b3b9, Addr:10.1.30.11) to Network 'PN/IE_1' Mapping Device/Node 'E1' (NodeID:49534400-9e59-4c19-996d-7ad00a2957e9, Addr:10.1.30.11) to Network 'PN/IE_1'
--> Found PLC in children: A40510 (ID: fc0d3bac-267e-488a-8dcf-7dc8599d80e8) --> Found PLC in children: A40510 (ID: fc0d3bac-267e-488a-8dcf-7dc8599d80e8)
--> Associating Network 'PN/IE_1' with PLC 'A40510' (via Node 'E1' Addr: 10.1.30.11) --> Associating Network 'PN/IE_1' with PLC 'A40510' (via Node 'E1' Addr: 10.1.30.11)
Mapping Device/Node 'IE1' (NodeID:c53ae31d-bee4-47ba-950d-15c31d2599b9, Addr:10.1.30.58) to Network 'PN/IE_1' Mapping Device/Node 'IE1' (NodeID:051911c8-9591-4119-a3ee-d6be687b7f9d, Addr:10.1.30.58) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:1a8366c9-7d4c-4e49-b0a3-77d445eabc8b, Addr:10.1.30.59) to Network 'PN/IE_1' Mapping Device/Node 'IE1' (NodeID:83519e1a-0912-4ae5-845b-b82bc743a92c, Addr:10.1.30.59) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:deeda41b-54d0-4c86-82c3-a311f753021e, Addr:10.1.30.60) to Network 'PN/IE_1' Mapping Device/Node 'IE1' (NodeID:2ba93b89-6b43-4a92-850b-8110b9c2efcc, Addr:10.1.30.60) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:8a916a9d-895e-4a12-9190-516ef6a8f191, Addr:10.1.30.61) to Network 'PN/IE_1' Mapping Device/Node 'IE1' (NodeID:b31787f3-2682-4d09-affc-69db1e2b89d3, Addr:10.1.30.61) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:c51471e0-9621-4ef7-b050-09bf4d695ea1, Addr:10.1.30.62) to Network 'PN/IE_1' Mapping Device/Node 'IE1' (NodeID:ef087f66-fd57-4be7-93a7-19497f4f852c, Addr:10.1.30.62) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:77ca312c-3dd0-46f6-bd64-5da69a99cf6f, Addr:10.1.30.63) to Network 'PN/IE_1' Mapping Device/Node 'IE1' (NodeID:6fe53dd5-7093-4245-923f-f4bbce67fabf, Addr:10.1.30.63) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:011019c6-5925-4544-87aa-27288c3aa70c, Addr:10.1.30.64) to Network 'PN/IE_1' Mapping Device/Node 'IE1' (NodeID:59f51321-8469-447f-ace9-05e1495bbafd, Addr:10.1.30.64) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:5ea1e894-b51c-44f9-8aff-80c58c6cb7ef, Addr:10.1.30.65) to Network 'PN/IE_1' Mapping Device/Node 'IE1' (NodeID:60c861c6-1889-4bde-9d16-1c625116aece, Addr:10.1.30.65) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:612916a9-7a26-4712-9de2-d3c7894db862, Addr:10.1.30.66) to Network 'PN/IE_1' Mapping Device/Node 'IE1' (NodeID:4f6ad448-3a75-4e48-8107-83634ba6d192, Addr:10.1.30.66) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:7be7cf9f-f7af-418c-8fe4-96b4a97a581d, Addr:10.1.30.31) to Network 'PN/IE_1' Mapping Device/Node 'IE1' (NodeID:b2153a8e-6526-4831-b2a1-5cf3da07c062, Addr:10.1.30.31) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:135492a8-02ab-4236-92ce-7a5585538297, Addr:10.1.30.32) to Network 'PN/IE_1' Mapping Device/Node 'IE1' (NodeID:08066980-9963-4ca3-9a5d-1d9de9b611a3, Addr:10.1.30.32) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:b7a06147-6428-4d95-ba0d-834fad49a1ae, Addr:10.1.30.170) to Network 'PN/IE_1' Mapping Device/Node 'IE1' (NodeID:2acb9ba4-f0f9-4fc6-9fd9-4592c1c8ef5a, Addr:10.1.30.170) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:7ce83bdf-dc4d-40f0-85c8-a246dd2c44be, Addr:10.1.30.33) to Network 'PN/IE_1' Mapping Device/Node 'IE1' (NodeID:feda1ddb-816d-40bd-a88c-8b1bc310c424, Addr:10.1.30.33) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:e9a07506-2869-4c34-8541-ee021d1623f0, Addr:10.1.30.34) to Network 'PN/IE_1' Mapping Device/Node 'IE1' (NodeID:80a42e65-b1b1-4a48-b4ea-f95ca4032021, Addr:10.1.30.34) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:3dd0f886-c3d0-4628-804b-ae18cf5931e8, Addr:10.1.30.35) to Network 'PN/IE_1' Mapping Device/Node 'IE1' (NodeID:082ca69e-92aa-43c5-982b-2b134e5ca9ea, Addr:10.1.30.35) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:67c3fa72-a956-4363-9a4d-e3300d7d1429, Addr:10.1.30.36) to Network 'PN/IE_1' Mapping Device/Node 'IE1' (NodeID:96aef85c-66f1-4792-94fd-f9ad5bb41d62, Addr:10.1.30.36) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:b35e73e8-a9ad-47e2-8ad5-00442c7a2df7, Addr:10.1.30.40) to Network 'PN/IE_1' Mapping Device/Node 'IE1' (NodeID:9b791861-b139-4a7d-b7ba-ebe195bbaf2c, Addr:10.1.30.40) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:867d0580-06cd-4722-a66f-8c5559b624f5, Addr:10.1.30.44) to Network 'PN/IE_1' Mapping Device/Node 'IE1' (NodeID:5085a882-dc3c-4794-9c0a-4821a9ecbf24, Addr:10.1.30.44) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:6f1b4062-80ac-4d1a-b351-546c9d0157e2, Addr:10.1.30.41) to Network 'PN/IE_1' Mapping Device/Node 'IE1' (NodeID:0ea5739f-aa85-46b8-958f-9b6ce2960d6d, Addr:10.1.30.41) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:c0016a06-48cc-47af-83aa-d4a0a6cb44f6, Addr:10.1.30.42) to Network 'PN/IE_1' Mapping Device/Node 'IE1' (NodeID:1f6cec79-c24c-4b0f-8aec-47d49ea1ce81, Addr:10.1.30.42) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:07fa5e8f-ffcf-4d81-9897-5ff9f73d6125, Addr:10.1.30.43) to Network 'PN/IE_1' Mapping Device/Node 'IE1' (NodeID:ab6516fc-5061-437d-bdc1-529d79d3d42a, Addr:10.1.30.43) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:5e8a4449-c958-4397-8b71-877af262333b, Addr:10.1.30.37) to Network 'PN/IE_1' Mapping Device/Node 'IE1' (NodeID:3f7bbecb-2bb9-42e8-8668-4a102c98873b, Addr:10.1.30.37) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:79096325-dcb8-4650-bc44-2c9735a93f52, Addr:10.1.30.45) to Network 'PN/IE_1' Mapping Device/Node 'IE1' (NodeID:6807d26a-ff0f-4ce7-89b9-8785cf5310ae, Addr:10.1.30.45) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:957e5975-eafe-477c-a682-bebf330a2868, Addr:10.1.30.46) to Network 'PN/IE_1' Mapping Device/Node 'IE1' (NodeID:4c58e397-5fda-4076-bd52-374ba80460cb, Addr:10.1.30.46) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:379ccd79-27b4-4b53-a552-3da783bc5b25, Addr:10.1.30.47) to Network 'PN/IE_1' Mapping Device/Node 'IE1' (NodeID:36da4ca9-6dc1-489a-bbdc-d124bf125dad, Addr:10.1.30.47) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:b53dd719-e8af-431f-b837-a0903ffb3a76, Addr:10.1.30.48) to Network 'PN/IE_1' Mapping Device/Node 'IE1' (NodeID:02213f0f-cc1f-4d97-ba86-eb3d11f7cb5b, Addr:10.1.30.48) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:0f28d88a-5208-4fc4-8ee1-f1bb33a947e8, Addr:10.1.30.49) to Network 'PN/IE_1' Mapping Device/Node 'IE1' (NodeID:628bab85-5b77-4335-9444-48f51dec6e66, Addr:10.1.30.49) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:d1f8ea18-50d2-410e-9966-136d8a79471d, Addr:10.1.30.70) to Network 'PN/IE_1' Mapping Device/Node 'IE1' (NodeID:5800b36d-59bb-43f6-8857-5b616730246d, Addr:10.1.30.70) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:1c86a627-5646-45e7-9c21-5d18d6544568, Addr:10.1.30.71) to Network 'PN/IE_1' Mapping Device/Node 'IE1' (NodeID:b68a427c-e7f6-459a-9e6a-39b76a4497ad, Addr:10.1.30.71) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:e80a9939-59d7-44e0-9a46-1fade44e1b78, Addr:10.1.30.72) to Network 'PN/IE_1' Mapping Device/Node 'IE1' (NodeID:d4bad7e1-0461-4dbb-b797-cb45a75225d9, Addr:10.1.30.72) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:8c51fa26-883a-468c-8c36-c0e1b31852e4, Addr:10.1.30.74) to Network 'PN/IE_1' Mapping Device/Node 'IE1' (NodeID:145f1526-6b77-4ea7-968b-de89a7bbeda2, Addr:10.1.30.74) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:17be2ccc-dede-4187-ba77-1ad8499a7349, Addr:10.1.30.73) to Network 'PN/IE_1' Mapping Device/Node 'IE1' (NodeID:8cde4014-94bb-47be-aacd-32f1915152d2, Addr:10.1.30.73) to Network 'PN/IE_1'
Data extraction and structuring complete. Data extraction and structuring complete.
Generating JSON output: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01.hierarchical.json Generating JSON output: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01.hierarchical.json
JSON data written successfully. JSON data written successfully.

View File

@ -1,18 +1,21 @@
--- Log de Ejecución: x3_excel_to_md.py --- --- Log de Ejecución: x3_excel_to_md.py ---
Grupo: IO_adaptation Grupo: IO_adaptation
Directorio de Trabajo: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\TAGsIO\v2 Directorio de Trabajo: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia
Inicio: 2025-05-15 11:58:03 Inicio: 2025-06-08 13:21:43
Fin: 2025-05-15 11:58:05 Fin: 2025-06-08 13:22:12
Duración: 0:00:01.664065 Duración: 0:00:29.516302
Estado: SUCCESS (Código de Salida: 0) Estado: SUCCESS (Código de Salida: 0)
--- SALIDA ESTÁNDAR (STDOUT) --- --- SALIDA ESTÁNDAR (STDOUT) ---
Usando directorio de trabajo: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\TAGsIO\v2 Usando directorio de trabajo: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia
Configuración de paths cargada desde: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\TAGsIO\v2\io_paths_config.json Configuración de paths cargada desde: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\io_paths_config.json
Usando archivo Excel predeterminado: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\TAGsIO\v2\PLCTags.xlsx Archivo PLCTags.xlsx no encontrado. Seleccione el archivo Excel exportado de TIA Portal:
Procesando archivo Excel: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\TAGsIO\v2\PLCTags.xlsx... Procesando archivo Excel: D:/Trabajo/VM/44 - 98050 - Fiera/Reporte/ExportsTia/PLCTagsv_02.xlsx...
Paths configurados para procesar: ['Inputs', 'Outputs', 'OutputsFesto', 'IO Not in Hardware\\InputsMaster', 'IO Not in Hardware\\OutputsMaster'] Paths configurados para procesar: ['Inputs', 'Outputs', 'OutputsFesto', 'IO Not in Hardware\\InputsMaster', 'IO Not in Hardware\\OutputsMaster']
¡Éxito! Archivo Excel convertido a Markdown en: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\TAGsIO\v2\Master IO Tags.md No se encontraron entradas para el path: OutputsFesto
No se encontraron entradas para el path: IO Not in Hardware\InputsMaster
No se encontraron entradas para el path: IO Not in Hardware\OutputsMaster
¡Éxito! Archivo Excel convertido a Markdown en: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\Master IO Tags.md
--- ERRORES (STDERR) --- --- ERRORES (STDERR) ---
Ninguno Ninguno

View File

@ -1,19 +1,19 @@
--- Log de Ejecución: x4_prompt_generator.py --- --- Log de Ejecución: x4_prompt_generator.py ---
Grupo: IO_adaptation Grupo: IO_adaptation
Directorio de Trabajo: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\TAGsIO\v2 Directorio de Trabajo: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia
Inicio: 2025-05-15 14:05:02 Inicio: 2025-06-08 11:05:58
Fin: 2025-05-15 14:05:04 Fin: 2025-06-08 11:06:03
Duración: 0:00:01.643930 Duración: 0:00:04.909042
Estado: SUCCESS (Código de Salida: 0) Estado: SUCCESS (Código de Salida: 0)
--- SALIDA ESTÁNDAR (STDOUT) --- --- SALIDA ESTÁNDAR (STDOUT) ---
Generador de prompt para adaptación de IO Generador de prompt para adaptación de IO
========================================= =========================================
Usando directorio de trabajo: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\TAGsIO\v2 Usando directorio de trabajo: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia
Usando ruta de Obsidian desde configuración: C:\Users\migue\OneDrive\Miguel\Obsidean\Trabajo\VM\04-SIDEL\00 - MASTER\MIXER\IO Usando ruta de Obsidian desde configuración: C:\Users\migue\OneDrive\Miguel\Obsidean\Trabajo\VM\04-SIDEL\00 - MASTER\MIXER\IO
Usando carpeta de equivalencias en Obsidian: C:\Users\migue\OneDrive\Miguel\Obsidean\Trabajo\VM\04-SIDEL\00 - MASTER\MIXER\IO Usando carpeta de equivalencias en Obsidian: C:\Users\migue\OneDrive\Miguel\Obsidean\Trabajo\VM\04-SIDEL\00 - MASTER\MIXER\IO
¡Prompt generado y copiado al portapapeles con éxito! ¡Prompt generado y copiado al portapapeles con éxito!
Prompt guardado en: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\TAGsIO\v2\IO_Adaptation_Prompt.txt Prompt guardado en: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\IO_Adaptation_Prompt.txt
--- ERRORES (STDERR) --- --- ERRORES (STDERR) ---
Ninguno Ninguno

View File

@ -0,0 +1,657 @@
--- Log de Ejecución: x7_update_CAx.py ---
Grupo: IO_adaptation
Directorio de Trabajo: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia
Inicio: 2025-06-07 18:57:44
Fin: 2025-06-07 18:57:53
Duración: 0:00:08.646157
Estado: SUCCESS (Código de Salida: 0)
--- SALIDA ESTÁNDAR (STDOUT) ---
--- Actualizador de AML desde Excel Modificado (v1.4 - Enhanced Address Element Search with Debug) ---
Directorio de trabajo: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia
1. Seleccione el archivo AML original:
2. Seleccione el archivo Excel modificado:
Archivo AML original: D:/Trabajo/VM/44 - 98050 - Fiera/Reporte/ExportsTia/98050_PLC_01.aml
Archivo Excel modificado: D:/Trabajo/VM/44 - 98050 - Fiera/Reporte/ExportsTia/A40510/Documentation/98050_PLC_01_IO_Report.xlsx
Usando directorio temporal: C:\Users\migue\AppData\Local\Temp\tmplzmtjbwx
Generando Excel de referencia desde AML original...
Pass 1: Found 363 InternalElement(s). Populating device dictionary...
Pass 2: Identifying PLCs and Networks (Refined v2)...
Identified Network: PN/IE_1 (6ce86626-0043-4a58-b675-cc13ac87121c) Type: Ethernet/Profinet
Identified PLC: A40510 (fc0d3bac-267e-488a-8dcf-7dc8599d80e8) - Type: CPU 1514SP T-2 PN OrderNo: 6ES7 514-2VN03-0AB0
Pass 3: Processing InternalLinks (Robust Network Mapping & IO)...
Found 103 InternalLink(s).
Mapping Device/Node 'E1' (NodeID:ab796923-4471-4a60-98f4-f8ea5920b3b9, Addr:10.1.30.11) to Network 'PN/IE_1'
--> Found PLC in children: A40510 (ID: fc0d3bac-267e-488a-8dcf-7dc8599d80e8)
--> Associating Network 'PN/IE_1' with PLC 'A40510' (via Node 'E1' Addr: 10.1.30.11)
Mapping Device/Node 'IE1' (NodeID:c53ae31d-bee4-47ba-950d-15c31d2599b9, Addr:10.1.30.58) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:1a8366c9-7d4c-4e49-b0a3-77d445eabc8b, Addr:10.1.30.59) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:deeda41b-54d0-4c86-82c3-a311f753021e, Addr:10.1.30.60) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:8a916a9d-895e-4a12-9190-516ef6a8f191, Addr:10.1.30.61) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:c51471e0-9621-4ef7-b050-09bf4d695ea1, Addr:10.1.30.62) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:77ca312c-3dd0-46f6-bd64-5da69a99cf6f, Addr:10.1.30.63) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:011019c6-5925-4544-87aa-27288c3aa70c, Addr:10.1.30.64) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:5ea1e894-b51c-44f9-8aff-80c58c6cb7ef, Addr:10.1.30.65) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:612916a9-7a26-4712-9de2-d3c7894db862, Addr:10.1.30.66) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:7be7cf9f-f7af-418c-8fe4-96b4a97a581d, Addr:10.1.30.31) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:135492a8-02ab-4236-92ce-7a5585538297, Addr:10.1.30.32) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:b7a06147-6428-4d95-ba0d-834fad49a1ae, Addr:10.1.30.170) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:7ce83bdf-dc4d-40f0-85c8-a246dd2c44be, Addr:10.1.30.33) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:e9a07506-2869-4c34-8541-ee021d1623f0, Addr:10.1.30.34) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:3dd0f886-c3d0-4628-804b-ae18cf5931e8, Addr:10.1.30.35) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:67c3fa72-a956-4363-9a4d-e3300d7d1429, Addr:10.1.30.36) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:b35e73e8-a9ad-47e2-8ad5-00442c7a2df7, Addr:10.1.30.40) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:867d0580-06cd-4722-a66f-8c5559b624f5, Addr:10.1.30.44) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:6f1b4062-80ac-4d1a-b351-546c9d0157e2, Addr:10.1.30.41) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:c0016a06-48cc-47af-83aa-d4a0a6cb44f6, Addr:10.1.30.42) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:07fa5e8f-ffcf-4d81-9897-5ff9f73d6125, Addr:10.1.30.43) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:5e8a4449-c958-4397-8b71-877af262333b, Addr:10.1.30.37) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:79096325-dcb8-4650-bc44-2c9735a93f52, Addr:10.1.30.45) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:957e5975-eafe-477c-a682-bebf330a2868, Addr:10.1.30.46) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:379ccd79-27b4-4b53-a552-3da783bc5b25, Addr:10.1.30.47) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:b53dd719-e8af-431f-b837-a0903ffb3a76, Addr:10.1.30.48) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:0f28d88a-5208-4fc4-8ee1-f1bb33a947e8, Addr:10.1.30.49) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:d1f8ea18-50d2-410e-9966-136d8a79471d, Addr:10.1.30.70) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:1c86a627-5646-45e7-9c21-5d18d6544568, Addr:10.1.30.71) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:e80a9939-59d7-44e0-9a46-1fade44e1b78, Addr:10.1.30.72) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:8c51fa26-883a-468c-8c36-c0e1b31852e4, Addr:10.1.30.74) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:17be2ccc-dede-4187-ba77-1ad8499a7349, Addr:10.1.30.73) to Network 'PN/IE_1'
Data extraction and structuring complete.
Generating Excel IO report for PLC: A40510
Excel IO report saved to: C:\Users\migue\AppData\Local\Temp\tmplzmtjbwx\temp_plc_fc0d3bac-267e-488a-8dcf-7dc8599d80e8.xlsx
Total rows in report: 33
Comparando archivos Excel...
Estructura básica validada correctamente.
Detectados 22 dispositivos con cambios.
A40510+U30210-AxisY: ['IO Input Start Address', 'IO Output Start Address']
A40510+E44010-Encoder: ['IO Input Start Address', 'IO Output Start Address']
A40510+U30310: ['IO Input Start Address', 'IO Output Start Address']
A40510+U30410: ['IO Input Start Address', 'IO Output Start Address']
A40510+U30510: ['IO Input Start Address', 'IO Output Start Address']
A40510+U30610: ['IO Input Start Address', 'IO Output Start Address']
A40510+M31010: ['IO Input Start Address', 'IO Output Start Address']
A40510+M31410: ['IO Input Start Address', 'IO Output Start Address']
A40510+M31110: ['IO Input Start Address', 'IO Output Start Address']
A40510+M31210: ['IO Input Start Address', 'IO Output Start Address']
A40510+M31310: ['IO Input Start Address', 'IO Output Start Address']
A40510+M30710: ['IO Input Start Address', 'IO Output Start Address']
A40510+M31510: ['IO Input Start Address', 'IO Output Start Address']
A40510+M31610: ['IO Input Start Address', 'IO Output Start Address']
A40510+M31710: ['IO Input Start Address', 'IO Output Start Address']
A40510+M31810: ['IO Input Start Address', 'IO Output Start Address']
A40510+M31910: ['IO Input Start Address', 'IO Output Start Address']
A40510+M34010: ['IO Input Start Address', 'IO Output Start Address']
A40510+M34110: ['IO Input Start Address', 'IO Output Start Address']
A40510+M34210: ['IO Input Start Address', 'IO Output Start Address']
A40510+M34410: ['IO Input Start Address', 'IO Output Start Address']
A40510+M34310: ['IO Input Start Address', 'IO Output Start Address']
Cargando archivo AML original...
Aplicando cambios al archivo AML...
Procesando cambios para: A40510+U30210-AxisY
Debug: Encontrado dispositivo - node_id: 135492a8-02ab-4236-92ce-7a5585538297, display_id: 0eef2113-ec3f-4c2f-8448-fa7456a6140d
Debug: Usando elemento XML con ID 0eef2113-ec3f-4c2f-8448-fa7456a6140d
Debug: Buscando elementos Address en dispositivo U30210-AxisY
Debug: No se encontró Address directo, buscando en elementos hijos...
Debug: No se encontró Address en hijos, buscando en elementos padre...
Debug: Encontrados 1 elemento(s) Address
Debug: Explorando Address element...
Debug: Encontrados 2 sub-elementos en Address
Debug: Revisando sub-elemento: 1
Debug: Encontrado IoType: Input
Debug: ¡Encontrado elemento Input compatible!
✓ IO Input Start Address actualizada: 120 -> 112
Debug: Buscando elementos Address en dispositivo U30210-AxisY
Debug: No se encontró Address directo, buscando en elementos hijos...
Debug: No se encontró Address en hijos, buscando en elementos padre...
Debug: Encontrados 1 elemento(s) Address
Debug: Explorando Address element...
Debug: Encontrados 2 sub-elementos en Address
Debug: Revisando sub-elemento: 1
Debug: Encontrado IoType: Input
Debug: Revisando sub-elemento: 2
Debug: Encontrado IoType: Output
Debug: ¡Encontrado elemento Output compatible!
✓ IO Output Start Address actualizada: 120 -> 112
Procesando cambios para: A40510+E44010-Encoder
Debug: Encontrado dispositivo - node_id: b7a06147-6428-4d95-ba0d-834fad49a1ae, display_id: e59b0b92-6c37-45d5-aa9b-8dae625e5272
Debug: Usando elemento XML con ID e59b0b92-6c37-45d5-aa9b-8dae625e5272
Debug: Buscando elementos Address en dispositivo E44010-Encoder
Debug: No se encontró Address directo, buscando en elementos hijos...
Debug: No se encontró Address en hijos, buscando en elementos padre...
Debug: Encontrados 1 elemento(s) Address
Debug: Explorando Address element...
Debug: Encontrados 2 sub-elementos en Address
Debug: Revisando sub-elemento: 1
Debug: Encontrado IoType: Input
Debug: ¡Encontrado elemento Input compatible!
✓ IO Input Start Address actualizada: 140 -> 574
Debug: Buscando elementos Address en dispositivo E44010-Encoder
Debug: No se encontró Address directo, buscando en elementos hijos...
Debug: No se encontró Address en hijos, buscando en elementos padre...
Debug: Encontrados 1 elemento(s) Address
Debug: Explorando Address element...
Debug: Encontrados 2 sub-elementos en Address
Debug: Revisando sub-elemento: 1
Debug: Encontrado IoType: Input
Debug: Revisando sub-elemento: 2
Debug: Encontrado IoType: Output
Debug: ¡Encontrado elemento Output compatible!
✓ IO Output Start Address actualizada: 140 -> 574
Procesando cambios para: A40510+U30310
Debug: Encontrado dispositivo - node_id: 7ce83bdf-dc4d-40f0-85c8-a246dd2c44be, display_id: 98a3e87d-176b-42b9-a606-fd97b54bc54b
Debug: Usando elemento XML con ID 98a3e87d-176b-42b9-a606-fd97b54bc54b
Debug: Buscando elementos Address en dispositivo U30310
Debug: No se encontró Address directo, buscando en elementos hijos...
Debug: No se encontró Address en hijos, buscando en elementos padre...
Debug: Encontrados 1 elemento(s) Address
Debug: Explorando Address element...
Debug: Encontrados 2 sub-elementos en Address
Debug: Revisando sub-elemento: 1
Debug: Encontrado IoType: Input
Debug: ¡Encontrado elemento Input compatible!
✓ IO Input Start Address actualizada: 4 -> 124
Debug: Buscando elementos Address en dispositivo U30310
Debug: No se encontró Address directo, buscando en elementos hijos...
Debug: No se encontró Address en hijos, buscando en elementos padre...
Debug: Encontrados 1 elemento(s) Address
Debug: Explorando Address element...
Debug: Encontrados 2 sub-elementos en Address
Debug: Revisando sub-elemento: 1
Debug: Encontrado IoType: Input
Debug: Revisando sub-elemento: 2
Debug: Encontrado IoType: Output
Debug: ¡Encontrado elemento Output compatible!
✓ IO Output Start Address actualizada: 3 -> 124
Procesando cambios para: A40510+U30410
Debug: Encontrado dispositivo - node_id: e9a07506-2869-4c34-8541-ee021d1623f0, display_id: 1fcdf8b4-7c90-48e0-bb72-f29eef363705
Debug: Usando elemento XML con ID 1fcdf8b4-7c90-48e0-bb72-f29eef363705
Debug: Buscando elementos Address en dispositivo U30410
Debug: No se encontró Address directo, buscando en elementos hijos...
Debug: No se encontró Address en hijos, buscando en elementos padre...
Debug: Encontrados 1 elemento(s) Address
Debug: Explorando Address element...
Debug: Encontrados 2 sub-elementos en Address
Debug: Revisando sub-elemento: 1
Debug: Encontrado IoType: Input
Debug: ¡Encontrado elemento Input compatible!
✓ IO Input Start Address actualizada: 52 -> 150
Debug: Buscando elementos Address en dispositivo U30410
Debug: No se encontró Address directo, buscando en elementos hijos...
Debug: No se encontró Address en hijos, buscando en elementos padre...
Debug: Encontrados 1 elemento(s) Address
Debug: Explorando Address element...
Debug: Encontrados 2 sub-elementos en Address
Debug: Revisando sub-elemento: 1
Debug: Encontrado IoType: Input
Debug: Revisando sub-elemento: 2
Debug: Encontrado IoType: Output
Debug: ¡Encontrado elemento Output compatible!
✓ IO Output Start Address actualizada: 51 -> 150
Procesando cambios para: A40510+U30510
Debug: Encontrado dispositivo - node_id: 3dd0f886-c3d0-4628-804b-ae18cf5931e8, display_id: 44004215-6e6f-456c-9c97-164ee1f8e7b7
Debug: Usando elemento XML con ID 44004215-6e6f-456c-9c97-164ee1f8e7b7
Debug: Buscando elementos Address en dispositivo U30510
Debug: No se encontró Address directo, buscando en elementos hijos...
Debug: No se encontró Address en hijos, buscando en elementos padre...
Debug: Encontrados 1 elemento(s) Address
Debug: Explorando Address element...
Debug: Encontrados 2 sub-elementos en Address
Debug: Revisando sub-elemento: 1
Debug: Encontrado IoType: Input
Debug: ¡Encontrado elemento Input compatible!
✓ IO Input Start Address actualizada: 156 -> 176
Debug: Buscando elementos Address en dispositivo U30510
Debug: No se encontró Address directo, buscando en elementos hijos...
Debug: No se encontró Address en hijos, buscando en elementos padre...
Debug: Encontrados 1 elemento(s) Address
Debug: Explorando Address element...
Debug: Encontrados 2 sub-elementos en Address
Debug: Revisando sub-elemento: 1
Debug: Encontrado IoType: Input
Debug: Revisando sub-elemento: 2
Debug: Encontrado IoType: Output
Debug: ¡Encontrado elemento Output compatible!
✓ IO Output Start Address actualizada: 144 -> 176
Procesando cambios para: A40510+U30610
Debug: Encontrado dispositivo - node_id: 67c3fa72-a956-4363-9a4d-e3300d7d1429, display_id: 2ea5ad51-0411-413d-8bb5-dfd0655592cf
Debug: Usando elemento XML con ID 2ea5ad51-0411-413d-8bb5-dfd0655592cf
Debug: Buscando elementos Address en dispositivo U30610
Debug: No se encontró Address directo, buscando en elementos hijos...
Debug: No se encontró Address en hijos, buscando en elementos padre...
Debug: Encontrados 1 elemento(s) Address
Debug: Explorando Address element...
Debug: Encontrados 2 sub-elementos en Address
Debug: Revisando sub-elemento: 1
Debug: Encontrado IoType: Input
Debug: ¡Encontrado elemento Input compatible!
✓ IO Input Start Address actualizada: 204 -> 202
Debug: Buscando elementos Address en dispositivo U30610
Debug: No se encontró Address directo, buscando en elementos hijos...
Debug: No se encontró Address en hijos, buscando en elementos padre...
Debug: Encontrados 1 elemento(s) Address
Debug: Explorando Address element...
Debug: Encontrados 2 sub-elementos en Address
Debug: Revisando sub-elemento: 1
Debug: Encontrado IoType: Input
Debug: Revisando sub-elemento: 2
Debug: Encontrado IoType: Output
Debug: ¡Encontrado elemento Output compatible!
✓ IO Output Start Address actualizada: 192 -> 202
Procesando cambios para: A40510+M31010
Debug: Encontrado dispositivo - node_id: b35e73e8-a9ad-47e2-8ad5-00442c7a2df7, display_id: 4e0722c0-9114-40bf-8452-733e5d275591
Debug: Usando elemento XML con ID 4e0722c0-9114-40bf-8452-733e5d275591
Debug: Buscando elementos Address en dispositivo M31010
Debug: No se encontró Address directo, buscando en elementos hijos...
Debug: No se encontró Address en hijos, buscando en elementos padre...
Debug: Encontrados 1 elemento(s) Address
Debug: Explorando Address element...
Debug: Encontrados 2 sub-elementos en Address
Debug: Revisando sub-elemento: 1
Debug: Encontrado IoType: Input
Debug: ¡Encontrado elemento Input compatible!
✓ IO Input Start Address actualizada: 272 -> 252
Debug: Buscando elementos Address en dispositivo M31010
Debug: No se encontró Address directo, buscando en elementos hijos...
Debug: No se encontró Address en hijos, buscando en elementos padre...
Debug: Encontrados 1 elemento(s) Address
Debug: Explorando Address element...
Debug: Encontrados 2 sub-elementos en Address
Debug: Revisando sub-elemento: 1
Debug: Encontrado IoType: Input
Debug: Revisando sub-elemento: 2
Debug: Encontrado IoType: Output
Debug: ¡Encontrado elemento Output compatible!
✓ IO Output Start Address actualizada: 272 -> 252
Procesando cambios para: A40510+M31410
Debug: Encontrado dispositivo - node_id: 867d0580-06cd-4722-a66f-8c5559b624f5, display_id: 6530fefc-0f00-45c5-b33a-9c009bf83e7b
Debug: Usando elemento XML con ID 6530fefc-0f00-45c5-b33a-9c009bf83e7b
Debug: Buscando elementos Address en dispositivo M31410
Debug: No se encontró Address directo, buscando en elementos hijos...
Debug: No se encontró Address en hijos, buscando en elementos padre...
Debug: Encontrados 1 elemento(s) Address
Debug: Explorando Address element...
Debug: Encontrados 2 sub-elementos en Address
Debug: Revisando sub-elemento: 1
Debug: Encontrado IoType: Input
Debug: ¡Encontrado elemento Input compatible!
✓ IO Input Start Address actualizada: 288 -> 334
Debug: Buscando elementos Address en dispositivo M31410
Debug: No se encontró Address directo, buscando en elementos hijos...
Debug: No se encontró Address en hijos, buscando en elementos padre...
Debug: Encontrados 1 elemento(s) Address
Debug: Explorando Address element...
Debug: Encontrados 2 sub-elementos en Address
Debug: Revisando sub-elemento: 1
Debug: Encontrado IoType: Input
Debug: Revisando sub-elemento: 2
Debug: Encontrado IoType: Output
Debug: ¡Encontrado elemento Output compatible!
✓ IO Output Start Address actualizada: 288 -> 334
Procesando cambios para: A40510+M31110
Debug: Encontrado dispositivo - node_id: 6f1b4062-80ac-4d1a-b351-546c9d0157e2, display_id: 95cedabf-d2b0-4d17-a36d-668d1b6db6d8
Debug: Usando elemento XML con ID 95cedabf-d2b0-4d17-a36d-668d1b6db6d8
Debug: Buscando elementos Address en dispositivo M31110
Debug: No se encontró Address directo, buscando en elementos hijos...
Debug: No se encontró Address en hijos, buscando en elementos padre...
Debug: Encontrados 1 elemento(s) Address
Debug: Explorando Address element...
Debug: Encontrados 2 sub-elementos en Address
Debug: Revisando sub-elemento: 1
Debug: Encontrado IoType: Input
Debug: ¡Encontrado elemento Input compatible!
✓ IO Input Start Address actualizada: 4000 -> 262
Debug: Buscando elementos Address en dispositivo M31110
Debug: No se encontró Address directo, buscando en elementos hijos...
Debug: No se encontró Address en hijos, buscando en elementos padre...
Debug: Encontrados 1 elemento(s) Address
Debug: Explorando Address element...
Debug: Encontrados 2 sub-elementos en Address
Debug: Revisando sub-elemento: 1
Debug: Encontrado IoType: Input
Debug: Revisando sub-elemento: 2
Debug: Encontrado IoType: Output
Debug: ¡Encontrado elemento Output compatible!
✓ IO Output Start Address actualizada: 4000 -> 262
Procesando cambios para: A40510+M31210
Debug: Encontrado dispositivo - node_id: c0016a06-48cc-47af-83aa-d4a0a6cb44f6, display_id: 1739a036-2231-4791-947b-9fa19eac922e
Debug: Usando elemento XML con ID 1739a036-2231-4791-947b-9fa19eac922e
Debug: Buscando elementos Address en dispositivo M31210
Debug: No se encontró Address directo, buscando en elementos hijos...
Debug: No se encontró Address en hijos, buscando en elementos padre...
Debug: Encontrados 1 elemento(s) Address
Debug: Explorando Address element...
Debug: Encontrados 2 sub-elementos en Address
Debug: Revisando sub-elemento: 1
Debug: Encontrado IoType: Input
Debug: ¡Encontrado elemento Input compatible!
✓ IO Input Start Address actualizada: 4022 -> 286
Debug: Buscando elementos Address en dispositivo M31210
Debug: No se encontró Address directo, buscando en elementos hijos...
Debug: No se encontró Address en hijos, buscando en elementos padre...
Debug: Encontrados 1 elemento(s) Address
Debug: Explorando Address element...
Debug: Encontrados 2 sub-elementos en Address
Debug: Revisando sub-elemento: 1
Debug: Encontrado IoType: Input
Debug: Revisando sub-elemento: 2
Debug: Encontrado IoType: Output
Debug: ¡Encontrado elemento Output compatible!
✓ IO Output Start Address actualizada: 4044 -> 286
Procesando cambios para: A40510+M31310
Debug: Encontrado dispositivo - node_id: 07fa5e8f-ffcf-4d81-9897-5ff9f73d6125, display_id: 90c971fa-0bdd-4dba-8d04-f6eb4dd940cc
Debug: Usando elemento XML con ID 90c971fa-0bdd-4dba-8d04-f6eb4dd940cc
Debug: Buscando elementos Address en dispositivo M31310
Debug: No se encontró Address directo, buscando en elementos hijos...
Debug: No se encontró Address en hijos, buscando en elementos padre...
Debug: Encontrados 1 elemento(s) Address
Debug: Explorando Address element...
Debug: Encontrados 2 sub-elementos en Address
Debug: Revisando sub-elemento: 1
Debug: Encontrado IoType: Input
Debug: ¡Encontrado elemento Input compatible!
✓ IO Input Start Address actualizada: 4044 -> 310
Debug: Buscando elementos Address en dispositivo M31310
Debug: No se encontró Address directo, buscando en elementos hijos...
Debug: No se encontró Address en hijos, buscando en elementos padre...
Debug: Encontrados 1 elemento(s) Address
Debug: Explorando Address element...
Debug: Encontrados 2 sub-elementos en Address
Debug: Revisando sub-elemento: 1
Debug: Encontrado IoType: Input
Debug: Revisando sub-elemento: 2
Debug: Encontrado IoType: Output
Debug: ¡Encontrado elemento Output compatible!
✓ IO Output Start Address actualizada: 4088 -> 310
Procesando cambios para: A40510+M30710
Debug: Encontrado dispositivo - node_id: 5e8a4449-c958-4397-8b71-877af262333b, display_id: b2f6e277-b969-4581-b3ed-4ca0bafe9994
Debug: Usando elemento XML con ID b2f6e277-b969-4581-b3ed-4ca0bafe9994
Debug: Buscando elementos Address en dispositivo M30710
Debug: No se encontró Address directo, buscando en elementos hijos...
Debug: No se encontró Address en hijos, buscando en elementos padre...
Debug: Encontrados 1 elemento(s) Address
Debug: Explorando Address element...
Debug: Encontrados 2 sub-elementos en Address
Debug: Revisando sub-elemento: 1
Debug: Encontrado IoType: Input
Debug: ¡Encontrado elemento Input compatible!
✓ IO Input Start Address actualizada: 4066 -> 228
Debug: Buscando elementos Address en dispositivo M30710
Debug: No se encontró Address directo, buscando en elementos hijos...
Debug: No se encontró Address en hijos, buscando en elementos padre...
Debug: Encontrados 1 elemento(s) Address
Debug: Explorando Address element...
Debug: Encontrados 2 sub-elementos en Address
Debug: Revisando sub-elemento: 1
Debug: Encontrado IoType: Input
Debug: Revisando sub-elemento: 2
Debug: Encontrado IoType: Output
Debug: ¡Encontrado elemento Output compatible!
✓ IO Output Start Address actualizada: 4132 -> 228
Procesando cambios para: A40510+M31510
Debug: Encontrado dispositivo - node_id: 79096325-dcb8-4650-bc44-2c9735a93f52, display_id: d6fa2c62-51ab-4f68-a0f9-d86b2d52c1a9
Debug: Usando elemento XML con ID d6fa2c62-51ab-4f68-a0f9-d86b2d52c1a9
Debug: Buscando elementos Address en dispositivo M31510
Debug: No se encontró Address directo, buscando en elementos hijos...
Debug: No se encontró Address en hijos, buscando en elementos padre...
Debug: Encontrados 1 elemento(s) Address
Debug: Explorando Address element...
Debug: Encontrados 2 sub-elementos en Address
Debug: Revisando sub-elemento: 1
Debug: Encontrado IoType: Input
Debug: ¡Encontrado elemento Input compatible!
✓ IO Input Start Address actualizada: 304 -> 344
Debug: Buscando elementos Address en dispositivo M31510
Debug: No se encontró Address directo, buscando en elementos hijos...
Debug: No se encontró Address en hijos, buscando en elementos padre...
Debug: Encontrados 1 elemento(s) Address
Debug: Explorando Address element...
Debug: Encontrados 2 sub-elementos en Address
Debug: Revisando sub-elemento: 1
Debug: Encontrado IoType: Input
Debug: Revisando sub-elemento: 2
Debug: Encontrado IoType: Output
Debug: ¡Encontrado elemento Output compatible!
✓ IO Output Start Address actualizada: 304 -> 344
Procesando cambios para: A40510+M31610
Debug: Encontrado dispositivo - node_id: 957e5975-eafe-477c-a682-bebf330a2868, display_id: 0a03a25f-121e-4e6a-ab81-f017d170ba52
Debug: Usando elemento XML con ID 0a03a25f-121e-4e6a-ab81-f017d170ba52
Debug: Buscando elementos Address en dispositivo M31610
Debug: No se encontró Address directo, buscando en elementos hijos...
Debug: No se encontró Address en hijos, buscando en elementos padre...
Debug: Encontrados 1 elemento(s) Address
Debug: Explorando Address element...
Debug: Encontrados 2 sub-elementos en Address
Debug: Revisando sub-elemento: 1
Debug: Encontrado IoType: Input
Debug: ¡Encontrado elemento Input compatible!
✓ IO Input Start Address actualizada: 4088 -> 354
Debug: Buscando elementos Address en dispositivo M31610
Debug: No se encontró Address directo, buscando en elementos hijos...
Debug: No se encontró Address en hijos, buscando en elementos padre...
Debug: Encontrados 1 elemento(s) Address
Debug: Explorando Address element...
Debug: Encontrados 2 sub-elementos en Address
Debug: Revisando sub-elemento: 1
Debug: Encontrado IoType: Input
Debug: Revisando sub-elemento: 2
Debug: Encontrado IoType: Output
Debug: ¡Encontrado elemento Output compatible!
✓ IO Output Start Address actualizada: 4176 -> 354
Procesando cambios para: A40510+M31710
Debug: Encontrado dispositivo - node_id: 379ccd79-27b4-4b53-a552-3da783bc5b25, display_id: 8d6214bd-7a77-462f-a2bf-8de1dccc0db2
Debug: Usando elemento XML con ID 8d6214bd-7a77-462f-a2bf-8de1dccc0db2
Debug: Buscando elementos Address en dispositivo M31710
Debug: No se encontró Address directo, buscando en elementos hijos...
Debug: No se encontró Address en hijos, buscando en elementos padre...
Debug: Encontrados 1 elemento(s) Address
Debug: Explorando Address element...
Debug: Encontrados 2 sub-elementos en Address
Debug: Revisando sub-elemento: 1
Debug: Encontrado IoType: Input
Debug: ¡Encontrado elemento Input compatible!
✓ IO Input Start Address actualizada: 4110 -> 378
Debug: Buscando elementos Address en dispositivo M31710
Debug: No se encontró Address directo, buscando en elementos hijos...
Debug: No se encontró Address en hijos, buscando en elementos padre...
Debug: Encontrados 1 elemento(s) Address
Debug: Explorando Address element...
Debug: Encontrados 2 sub-elementos en Address
Debug: Revisando sub-elemento: 1
Debug: Encontrado IoType: Input
Debug: Revisando sub-elemento: 2
Debug: Encontrado IoType: Output
Debug: ¡Encontrado elemento Output compatible!
✓ IO Output Start Address actualizada: 4220 -> 378
Procesando cambios para: A40510+M31810
Debug: Encontrado dispositivo - node_id: b53dd719-e8af-431f-b837-a0903ffb3a76, display_id: 9d4ba8bc-a7e7-40a0-99d1-f2fd88a6b00d
Debug: Usando elemento XML con ID 9d4ba8bc-a7e7-40a0-99d1-f2fd88a6b00d
Debug: Buscando elementos Address en dispositivo M31810
Debug: No se encontró Address directo, buscando en elementos hijos...
Debug: No se encontró Address en hijos, buscando en elementos padre...
Debug: Encontrados 1 elemento(s) Address
Debug: Explorando Address element...
Debug: Encontrados 2 sub-elementos en Address
Debug: Revisando sub-elemento: 1
Debug: Encontrado IoType: Input
Debug: ¡Encontrado elemento Input compatible!
✓ IO Input Start Address actualizada: 4132 -> 402
Debug: Buscando elementos Address en dispositivo M31810
Debug: No se encontró Address directo, buscando en elementos hijos...
Debug: No se encontró Address en hijos, buscando en elementos padre...
Debug: Encontrados 1 elemento(s) Address
Debug: Explorando Address element...
Debug: Encontrados 2 sub-elementos en Address
Debug: Revisando sub-elemento: 1
Debug: Encontrado IoType: Input
Debug: Revisando sub-elemento: 2
Debug: Encontrado IoType: Output
Debug: ¡Encontrado elemento Output compatible!
✓ IO Output Start Address actualizada: 4264 -> 402
Procesando cambios para: A40510+M31910
Debug: Encontrado dispositivo - node_id: 0f28d88a-5208-4fc4-8ee1-f1bb33a947e8, display_id: 0c0c14f0-df2b-4c5e-8163-f7cb58788dae
Debug: Usando elemento XML con ID 0c0c14f0-df2b-4c5e-8163-f7cb58788dae
Debug: Buscando elementos Address en dispositivo M31910
Debug: No se encontró Address directo, buscando en elementos hijos...
Debug: No se encontró Address en hijos, buscando en elementos padre...
Debug: Encontrados 1 elemento(s) Address
Debug: Explorando Address element...
Debug: Encontrados 2 sub-elementos en Address
Debug: Revisando sub-elemento: 1
Debug: Encontrado IoType: Input
Debug: ¡Encontrado elemento Input compatible!
✓ IO Input Start Address actualizada: 4154 -> 426
Debug: Buscando elementos Address en dispositivo M31910
Debug: No se encontró Address directo, buscando en elementos hijos...
Debug: No se encontró Address en hijos, buscando en elementos padre...
Debug: Encontrados 1 elemento(s) Address
Debug: Explorando Address element...
Debug: Encontrados 2 sub-elementos en Address
Debug: Revisando sub-elemento: 1
Debug: Encontrado IoType: Input
Debug: Revisando sub-elemento: 2
Debug: Encontrado IoType: Output
Debug: ¡Encontrado elemento Output compatible!
✓ IO Output Start Address actualizada: 4308 -> 426
Procesando cambios para: A40510+M34010
Debug: Encontrado dispositivo - node_id: d1f8ea18-50d2-410e-9966-136d8a79471d, display_id: a01c051b-9abc-4947-9632-6dcad7da84b8
Debug: Usando elemento XML con ID a01c051b-9abc-4947-9632-6dcad7da84b8
Debug: Buscando elementos Address en dispositivo M34010
Debug: No se encontró Address directo, buscando en elementos hijos...
Debug: No se encontró Address en hijos, buscando en elementos padre...
Debug: Encontrados 1 elemento(s) Address
Debug: Explorando Address element...
Debug: Encontrados 2 sub-elementos en Address
Debug: Revisando sub-elemento: 1
Debug: Encontrado IoType: Input
Debug: ¡Encontrado elemento Input compatible!
✓ IO Input Start Address actualizada: 4176 -> 468
Debug: Buscando elementos Address en dispositivo M34010
Debug: No se encontró Address directo, buscando en elementos hijos...
Debug: No se encontró Address en hijos, buscando en elementos padre...
Debug: Encontrados 1 elemento(s) Address
Debug: Explorando Address element...
Debug: Encontrados 2 sub-elementos en Address
Debug: Revisando sub-elemento: 1
Debug: Encontrado IoType: Input
Debug: Revisando sub-elemento: 2
Debug: Encontrado IoType: Output
Debug: ¡Encontrado elemento Output compatible!
✓ IO Output Start Address actualizada: 4352 -> 468
Procesando cambios para: A40510+M34110
Debug: Encontrado dispositivo - node_id: 1c86a627-5646-45e7-9c21-5d18d6544568, display_id: d2440b5d-132e-4d66-986c-fe42a8ff4f74
Debug: Usando elemento XML con ID d2440b5d-132e-4d66-986c-fe42a8ff4f74
Debug: Buscando elementos Address en dispositivo M34110
Debug: No se encontró Address directo, buscando en elementos hijos...
Debug: No se encontró Address en hijos, buscando en elementos padre...
Debug: Encontrados 1 elemento(s) Address
Debug: Explorando Address element...
Debug: Encontrados 2 sub-elementos en Address
Debug: Revisando sub-elemento: 1
Debug: Encontrado IoType: Input
Debug: ¡Encontrado elemento Input compatible!
✓ IO Input Start Address actualizada: 4198 -> 492
Debug: Buscando elementos Address en dispositivo M34110
Debug: No se encontró Address directo, buscando en elementos hijos...
Debug: No se encontró Address en hijos, buscando en elementos padre...
Debug: Encontrados 1 elemento(s) Address
Debug: Explorando Address element...
Debug: Encontrados 2 sub-elementos en Address
Debug: Revisando sub-elemento: 1
Debug: Encontrado IoType: Input
Debug: Revisando sub-elemento: 2
Debug: Encontrado IoType: Output
Debug: ¡Encontrado elemento Output compatible!
✓ IO Output Start Address actualizada: 4396 -> 492
Procesando cambios para: A40510+M34210
Debug: Encontrado dispositivo - node_id: e80a9939-59d7-44e0-9a46-1fade44e1b78, display_id: 041fea68-1b2b-4087-be60-b254def81cf6
Debug: Usando elemento XML con ID 041fea68-1b2b-4087-be60-b254def81cf6
Debug: Buscando elementos Address en dispositivo M34210
Debug: No se encontró Address directo, buscando en elementos hijos...
Debug: No se encontró Address en hijos, buscando en elementos padre...
Debug: Encontrados 1 elemento(s) Address
Debug: Explorando Address element...
Debug: Encontrados 2 sub-elementos en Address
Debug: Revisando sub-elemento: 1
Debug: Encontrado IoType: Input
Debug: ¡Encontrado elemento Input compatible!
✓ IO Input Start Address actualizada: 4220 -> 516
Debug: Buscando elementos Address en dispositivo M34210
Debug: No se encontró Address directo, buscando en elementos hijos...
Debug: No se encontró Address en hijos, buscando en elementos padre...
Debug: Encontrados 1 elemento(s) Address
Debug: Explorando Address element...
Debug: Encontrados 2 sub-elementos en Address
Debug: Revisando sub-elemento: 1
Debug: Encontrado IoType: Input
Debug: Revisando sub-elemento: 2
Debug: Encontrado IoType: Output
Debug: ¡Encontrado elemento Output compatible!
✓ IO Output Start Address actualizada: 4440 -> 516
Procesando cambios para: A40510+M34410
Debug: Encontrado dispositivo - node_id: 8c51fa26-883a-468c-8c36-c0e1b31852e4, display_id: 28037fc6-dc45-4ec9-bea0-eafb922f6c6e
Debug: Usando elemento XML con ID 28037fc6-dc45-4ec9-bea0-eafb922f6c6e
Debug: Buscando elementos Address en dispositivo M34410
Debug: No se encontró Address directo, buscando en elementos hijos...
Debug: No se encontró Address en hijos, buscando en elementos padre...
Debug: Encontrados 1 elemento(s) Address
Debug: Explorando Address element...
Debug: Encontrados 2 sub-elementos en Address
Debug: Revisando sub-elemento: 1
Debug: Encontrado IoType: Input
Debug: ¡Encontrado elemento Input compatible!
✓ IO Input Start Address actualizada: 362 -> 564
Debug: Buscando elementos Address en dispositivo M34410
Debug: No se encontró Address directo, buscando en elementos hijos...
Debug: No se encontró Address en hijos, buscando en elementos padre...
Debug: Encontrados 1 elemento(s) Address
Debug: Explorando Address element...
Debug: Encontrados 2 sub-elementos en Address
Debug: Revisando sub-elemento: 1
Debug: Encontrado IoType: Input
Debug: Revisando sub-elemento: 2
Debug: Encontrado IoType: Output
Debug: ¡Encontrado elemento Output compatible!
✓ IO Output Start Address actualizada: 340 -> 564
Procesando cambios para: A40510+M34310
Debug: Encontrado dispositivo - node_id: 17be2ccc-dede-4187-ba77-1ad8499a7349, display_id: 7a77a815-8c59-4cfd-81fe-d851a8e57aea
Debug: Usando elemento XML con ID 7a77a815-8c59-4cfd-81fe-d851a8e57aea
Debug: Buscando elementos Address en dispositivo M34310
Debug: No se encontró Address directo, buscando en elementos hijos...
Debug: No se encontró Address en hijos, buscando en elementos padre...
Debug: Encontrados 1 elemento(s) Address
Debug: Explorando Address element...
Debug: Encontrados 2 sub-elementos en Address
Debug: Revisando sub-elemento: 1
Debug: Encontrado IoType: Input
Debug: ¡Encontrado elemento Input compatible!
✓ IO Input Start Address actualizada: 4242 -> 540
Debug: Buscando elementos Address en dispositivo M34310
Debug: No se encontró Address directo, buscando en elementos hijos...
Debug: No se encontró Address en hijos, buscando en elementos padre...
Debug: Encontrados 1 elemento(s) Address
Debug: Explorando Address element...
Debug: Encontrados 2 sub-elementos en Address
Debug: Revisando sub-elemento: 1
Debug: Encontrado IoType: Input
Debug: Revisando sub-elemento: 2
Debug: Encontrado IoType: Output
Debug: ¡Encontrado elemento Output compatible!
✓ IO Output Start Address actualizada: 4484 -> 540
Total de cambios aplicados: 44
Archivo AML actualizado guardado en: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01_updated.aml
¡Proceso completado exitosamente!
Archivo AML actualizado: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01_updated.aml
--- ERRORES (STDERR) ---
D:\Proyectos\Scripts\ParamManagerScripts\backend\script_groups\IO_adaptation\x7_update_CAx.py:532: FutureWarning: Truth-testing of elements was a source of confusion and will always return True in future versions. Use specific 'len(elem)' or 'elem is not None' test instead.
if target_io_element:
D:\Proyectos\Scripts\ParamManagerScripts\backend\script_groups\IO_adaptation\x7_update_CAx.py:535: FutureWarning: Truth-testing of elements was a source of confusion and will always return True in future versions. Use specific 'len(elem)' or 'elem is not None' test instead.
if not target_io_element:
--- FIN DEL LOG ---

View File

@ -70,5 +70,23 @@
"short_description": "Sin descripción corta.", "short_description": "Sin descripción corta.",
"long_description": "", "long_description": "",
"hidden": false "hidden": false
},
"debug_hierarchy.py": {
"display_name": "debug_hierarchy",
"short_description": "Sin descripción corta.",
"long_description": "",
"hidden": false
},
"debug_io_processing.py": {
"display_name": "debug_io_processing",
"short_description": "Sin descripción corta.",
"long_description": "",
"hidden": false
},
"debug_table_generation.py": {
"display_name": "debug_table_generation",
"short_description": "Sin descripción corta.",
"long_description": "",
"hidden": false
} }
} }

View File

@ -554,10 +554,131 @@ def generate_io_excel_report(project_data, excel_file_path, target_plc_id, outpu
# Lista para almacenar todas las filas del Excel # Lista para almacenar todas las filas del Excel
excel_rows = [] excel_rows = []
# v32.5: First, process PLC's local modules (modules in the same rack/structure as the PLC)
plc_parent_id = plc_info.get("parent_id")
if plc_parent_id:
# Find sibling modules in the same parent structure (rack)
for dev_id, dev_info in project_data.get("devices", {}).items():
if dev_info.get("parent_id") == plc_parent_id and dev_id != target_plc_id:
# This is a sibling module of the PLC
module_context = {
"id": dev_id,
"name": dev_info.get("name", dev_id),
"order_number": dev_info.get("order_number", "N/A"),
"type_name": dev_info.get("type_name", "N/A")
}
module_ios = find_io_recursively(dev_id, project_data, module_context)
if module_ios:
# Agrupar IOs por módulo para este módulo local
ios_by_module = {}
for addr_info in module_ios:
module_id = addr_info.get("module_id")
if module_id not in ios_by_module:
ios_by_module[module_id] = {
'module_info': {
'name': addr_info.get('module_name', '?'),
'type': addr_info.get('module_type_name', 'N/A'),
'order': addr_info.get('module_order_number', 'N/A'),
'position': addr_info.get('module_pos', 'N/A')
},
'inputs': [],
'outputs': []
}
# Clasificar IO como input u output
io_type = addr_info.get("type", "").lower()
if io_type == "input":
ios_by_module[module_id]['inputs'].append(addr_info)
elif io_type == "output":
ios_by_module[module_id]['outputs'].append(addr_info)
# Crear una fila por cada módulo local con IOs
for module_id, module_data in ios_by_module.items():
module_info = module_data['module_info']
# Calcular direcciones de entrada - Start + Word Count
input_start_addr = 'N/A'
input_word_count = 0
total_input_bits = 0
for addr_info in module_data['inputs']:
start_str = addr_info.get("start", "?")
length_str = addr_info.get("length", "?")
try:
start_byte = int(start_str)
length_bits = int(length_str)
length_bytes = math.ceil(length_bits / 8.0)
if length_bits > 0 and length_bytes == 0:
length_bytes = 1
# Para múltiples rangos, tomar el primer inicio y sumar words
if input_start_addr == 'N/A':
input_start_addr = start_byte
else:
input_start_addr = min(input_start_addr, start_byte)
# Convertir bytes a words (asumiendo words de 2 bytes)
word_count = math.ceil(length_bytes / 2.0)
input_word_count += word_count
total_input_bits += length_bits
except:
# En caso de error, mantener N/A
pass
# Calcular direcciones de salida - Start + Word Count
output_start_addr = 'N/A'
output_word_count = 0
total_output_bits = 0
for addr_info in module_data['outputs']:
start_str = addr_info.get("start", "?")
length_str = addr_info.get("length", "?")
try:
start_byte = int(start_str)
length_bits = int(length_str)
length_bytes = math.ceil(length_bits / 8.0)
if length_bits > 0 and length_bytes == 0:
length_bytes = 1
# Para múltiples rangos, tomar el primer inicio y sumar words
if output_start_addr == 'N/A':
output_start_addr = start_byte
else:
output_start_addr = min(output_start_addr, start_byte)
# Convertir bytes a words (asumiendo words de 2 bytes)
word_count = math.ceil(length_bytes / 2.0)
output_word_count += word_count
total_output_bits += length_bits
except:
# En caso de error, mantener N/A
pass
excel_rows.append({
'PLC Name': plc_name,
'Network Path': f"Local I/O -> {module_info['name']}",
'Network Type': 'Local I/O',
'Device Address': 'Local',
'Device Name': f"PLC {plc_name}",
'Device Type': module_info['type'],
'Order Number': module_info['order'],
'Firmware Version': 'N/A',
'Position': module_info['position'],
'IO Input Start Address': input_start_addr,
'IO Input Word Count': input_word_count if input_word_count > 0 else 'N/A',
'IO Output Start Address': output_start_addr,
'IO Output Word Count': output_word_count if output_word_count > 0 else 'N/A',
'Total Input Bits': total_input_bits,
'Total Output Bits': total_output_bits,
'Module Name': module_info['name'],
'Module Type': module_info['type'],
'Module Order Number': module_info['order']
})
# Procesar las redes conectadas al PLC # Procesar las redes conectadas al PLC
plc_networks = plc_info.get("connected_networks", {}) plc_networks = plc_info.get("connected_networks", {})
if not plc_networks: if not plc_networks and not excel_rows:
# Si no hay redes, crear una fila básica del PLC # Si no hay redes, crear una fila básica del PLC
excel_rows.append({ excel_rows.append({
'PLC Name': plc_name, 'PLC Name': plc_name,
@ -570,9 +691,9 @@ def generate_io_excel_report(project_data, excel_file_path, target_plc_id, outpu
'Firmware Version': plc_info.get("firmware_version", "N/A"), 'Firmware Version': plc_info.get("firmware_version", "N/A"),
'Position': plc_info.get("position", "N/A"), 'Position': plc_info.get("position", "N/A"),
'IO Input Start Address': 'N/A', 'IO Input Start Address': 'N/A',
'IO Input End Address': 'N/A', 'IO Input Word Count': 'N/A',
'IO Output Start Address': 'N/A', 'IO Output Start Address': 'N/A',
'IO Output End Address': 'N/A', 'IO Output Word Count': 'N/A',
'Total Input Bits': 0, 'Total Input Bits': 0,
'Total Output Bits': 0, 'Total Output Bits': 0,
'Module Name': 'N/A', 'Module Name': 'N/A',
@ -619,9 +740,9 @@ def generate_io_excel_report(project_data, excel_file_path, target_plc_id, outpu
'Firmware Version': plc_info.get("firmware_version", "N/A"), 'Firmware Version': plc_info.get("firmware_version", "N/A"),
'Position': plc_info.get("position", "N/A"), 'Position': plc_info.get("position", "N/A"),
'IO Input Start Address': 'N/A', 'IO Input Start Address': 'N/A',
'IO Input End Address': 'N/A', 'IO Input Word Count': 'N/A',
'IO Output Start Address': 'N/A', 'IO Output Start Address': 'N/A',
'IO Output End Address': 'N/A', 'IO Output Word Count': 'N/A',
'Total Input Bits': 0, 'Total Input Bits': 0,
'Total Output Bits': 0, 'Total Output Bits': 0,
'Module Name': 'PLC Main Unit', 'Module Name': 'PLC Main Unit',
@ -721,9 +842,9 @@ def generate_io_excel_report(project_data, excel_file_path, target_plc_id, outpu
for module_id, module_data in ios_by_module.items(): for module_id, module_data in ios_by_module.items():
module_info = module_data['module_info'] module_info = module_data['module_info']
# Calcular direcciones de entrada - formato simplificado # Calcular direcciones de entrada - Start + Word Count
input_start_addr = 'N/A' input_start_addr = 'N/A'
input_end_addr = 'N/A' input_word_count = 0
total_input_bits = 0 total_input_bits = 0
for addr_info in module_data['inputs']: for addr_info in module_data['inputs']:
@ -735,24 +856,24 @@ def generate_io_excel_report(project_data, excel_file_path, target_plc_id, outpu
length_bytes = math.ceil(length_bits / 8.0) length_bytes = math.ceil(length_bits / 8.0)
if length_bits > 0 and length_bytes == 0: if length_bits > 0 and length_bytes == 0:
length_bytes = 1 length_bytes = 1
end_byte = start_byte + length_bytes - 1
# Para múltiples rangos, tomar el primer inicio y último fin # Para múltiples rangos, tomar el primer inicio y sumar words
if input_start_addr == 'N/A': if input_start_addr == 'N/A':
input_start_addr = start_byte input_start_addr = start_byte
input_end_addr = end_byte
else: else:
input_start_addr = min(input_start_addr, start_byte) input_start_addr = min(input_start_addr, start_byte)
input_end_addr = max(input_end_addr, end_byte)
# Convertir bytes a words (asumiendo words de 2 bytes)
word_count = math.ceil(length_bytes / 2.0)
input_word_count += word_count
total_input_bits += length_bits total_input_bits += length_bits
except: except:
# En caso de error, mantener N/A # En caso de error, mantener N/A
pass pass
# Calcular direcciones de salida - formato simplificado # Calcular direcciones de salida - Start + Word Count
output_start_addr = 'N/A' output_start_addr = 'N/A'
output_end_addr = 'N/A' output_word_count = 0
total_output_bits = 0 total_output_bits = 0
for addr_info in module_data['outputs']: for addr_info in module_data['outputs']:
@ -764,16 +885,16 @@ def generate_io_excel_report(project_data, excel_file_path, target_plc_id, outpu
length_bytes = math.ceil(length_bits / 8.0) length_bytes = math.ceil(length_bits / 8.0)
if length_bits > 0 and length_bytes == 0: if length_bits > 0 and length_bytes == 0:
length_bytes = 1 length_bytes = 1
end_byte = start_byte + length_bytes - 1
# Para múltiples rangos, tomar el primer inicio y último fin # Para múltiples rangos, tomar el primer inicio y sumar words
if output_start_addr == 'N/A': if output_start_addr == 'N/A':
output_start_addr = start_byte output_start_addr = start_byte
output_end_addr = end_byte
else: else:
output_start_addr = min(output_start_addr, start_byte) output_start_addr = min(output_start_addr, start_byte)
output_end_addr = max(output_end_addr, end_byte)
# Convertir bytes a words (asumiendo words de 2 bytes)
word_count = math.ceil(length_bytes / 2.0)
output_word_count += word_count
total_output_bits += length_bits total_output_bits += length_bits
except: except:
# En caso de error, mantener N/A # En caso de error, mantener N/A
@ -790,9 +911,9 @@ def generate_io_excel_report(project_data, excel_file_path, target_plc_id, outpu
'Firmware Version': firmware_version, 'Firmware Version': firmware_version,
'Position': device_position, 'Position': device_position,
'IO Input Start Address': input_start_addr, 'IO Input Start Address': input_start_addr,
'IO Input End Address': input_end_addr, 'IO Input Word Count': input_word_count if input_word_count > 0 else 'N/A',
'IO Output Start Address': output_start_addr, 'IO Output Start Address': output_start_addr,
'IO Output End Address': output_end_addr, 'IO Output Word Count': output_word_count if output_word_count > 0 else 'N/A',
'Total Input Bits': total_input_bits, 'Total Input Bits': total_input_bits,
'Total Output Bits': total_output_bits, 'Total Output Bits': total_output_bits,
'Module Name': module_info['name'], 'Module Name': module_info['name'],
@ -812,9 +933,9 @@ def generate_io_excel_report(project_data, excel_file_path, target_plc_id, outpu
'Firmware Version': firmware_version, 'Firmware Version': firmware_version,
'Position': device_position, 'Position': device_position,
'IO Input Start Address': 'N/A', 'IO Input Start Address': 'N/A',
'IO Input End Address': 'N/A', 'IO Input Word Count': 'N/A',
'IO Output Start Address': 'N/A', 'IO Output Start Address': 'N/A',
'IO Output End Address': 'N/A', 'IO Output Word Count': 'N/A',
'Total Input Bits': 0, 'Total Input Bits': 0,
'Total Output Bits': 0, 'Total Output Bits': 0,
'Module Name': 'N/A', 'Module Name': 'N/A',
@ -834,12 +955,24 @@ def generate_io_excel_report(project_data, excel_file_path, target_plc_id, outpu
'PLC Name', 'Network Path', 'Network Type', 'Device Address', 'Device Name', 'PLC Name', 'Network Path', 'Network Type', 'Device Address', 'Device Name',
'Device Type', 'Order Number', 'Firmware Version', 'Position', 'Device Type', 'Order Number', 'Firmware Version', 'Position',
'Module Name', 'Module Type', 'Module Order Number', 'Module Name', 'Module Type', 'Module Order Number',
'IO Input Start Address', 'IO Input End Address', 'IO Output Start Address', 'IO Output End Address', 'IO Input Start Address', 'IO Input Word Count', 'IO Output Start Address', 'IO Output Word Count',
'Total Input Bits', 'Total Output Bits', 'Unique_ID' # Agregar al final para compatibilidad 'Total Input Bits', 'Total Output Bits', 'Unique_ID' # Agregar al final para compatibilidad
] ]
df = df.reindex(columns=column_order) df = df.reindex(columns=column_order)
try: try:
# Convertir columnas numéricas específicas a int para evitar decimales
numeric_columns = [
'IO Input Start Address', 'IO Input Word Count',
'IO Output Start Address', 'IO Output Word Count',
'Total Input Bits', 'Total Output Bits'
]
for col in numeric_columns:
if col in df.columns:
# Convertir a int manteniendo N/A como string
df[col] = df[col].apply(lambda x: int(x) if isinstance(x, (int, float)) and pd.notna(x) and x != 'N/A' else x)
# Guardar como Excel con formato # Guardar como Excel con formato
with pd.ExcelWriter(excel_file_path, engine='openpyxl') as writer: with pd.ExcelWriter(excel_file_path, engine='openpyxl') as writer:
df.to_excel(writer, sheet_name='IO Report', index=False) df.to_excel(writer, sheet_name='IO Report', index=False)
@ -904,6 +1037,92 @@ def generate_markdown_tree(project_data, md_file_path, target_plc_id, output_roo
if firmware and firmware != "N/A": if firmware and firmware != "N/A":
markdown_lines.append(f"- **Firmware:** `{firmware}`") markdown_lines.append(f"- **Firmware:** `{firmware}`")
# v32.5: Process PLC's local modules (modules in the same rack/structure as the PLC)
plc_local_modules_io = []
plc_parent_id = plc_info.get("parent_id")
if plc_parent_id:
# Find sibling modules in the same parent structure (rack)
for dev_id, dev_info in project_data.get("devices", {}).items():
if dev_info.get("parent_id") == plc_parent_id and dev_id != target_plc_id:
# This is a sibling module of the PLC
module_context = {
"id": dev_id,
"name": dev_info.get("name", dev_id),
"order_number": dev_info.get("order_number", "N/A"),
"type_name": dev_info.get("type_name", "N/A")
}
module_ios = find_io_recursively(dev_id, project_data, module_context)
if module_ios:
for addr_info in module_ios:
# Add to table data
length_bits = 0
siemens_addr = "FMT_ERROR"
try:
start_byte = int(addr_info.get("start", "0"))
length_bits = int(addr_info.get("length", "0"))
io_type = addr_info.get("type", "?")
length_bytes = math.ceil(length_bits / 8.0)
if length_bits > 0 and length_bytes == 0:
length_bytes = 1
end_byte = start_byte + length_bytes - 1
prefix = "P?"
if io_type.lower() == "input":
prefix = "EW"
elif io_type.lower() == "output":
prefix = "AW"
siemens_addr = f"{prefix} {start_byte}..{end_byte}"
except Exception:
siemens_addr = f"FMT_ERROR({addr_info.get('start', '?')},{addr_info.get('length', '?')})"
plc_local_modules_io.append({
"Network": "PLC Local Modules",
"Network Type": "Local I/O",
"Device Address": "Local",
"Device Name": f"PLC {plc_info.get('name', target_plc_id)}",
"Sub-Device": addr_info.get('module_name', '?'),
"Sub-Device OrderNo": addr_info.get('module_order_number', 'N/A'),
"Sub-Device Type": addr_info.get('module_type_name', 'N/A'),
"IO Type": addr_info.get("type", "?"),
"IO Address": siemens_addr,
"Number of Bits": length_bits,
"SortKey": (
"PLC Local Modules", # Network name for sorting
[0], # Device sort key (always first)
(
int(addr_info.get("module_pos", "9999"))
if str(addr_info.get("module_pos", "9999")).isdigit()
else 9999
),
addr_info.get("module_name", ""),
addr_info.get("type", ""),
(
int(addr_info.get("start", "0"))
if str(addr_info.get("start", "0")).isdigit()
else float("inf")
),
)
})
# Add PLC local modules to the main IO list
all_plc_io_for_table.extend(plc_local_modules_io)
# Show PLC local modules in markdown
if plc_local_modules_io:
markdown_lines.append("\n- **Local I/O Modules (same rack as PLC):**")
modules_by_name = {}
for io_data in plc_local_modules_io:
module_name = io_data["Sub-Device"]
if module_name not in modules_by_name:
modules_by_name[module_name] = []
modules_by_name[module_name].append(io_data)
for module_name in sorted(modules_by_name.keys()):
module_ios = modules_by_name[module_name]
first_io = module_ios[0]
markdown_lines.append(f" - **{module_name}** (Type: `{first_io['Sub-Device Type']}`, OrderNo: `{first_io['Sub-Device OrderNo']}`)")
for io_data in sorted(module_ios, key=lambda x: x['SortKey']):
markdown_lines.append(f" - `{io_data['IO Address']}` ({io_data['IO Type']}, {io_data['Number of Bits']} bits)")
plc_networks = plc_info.get("connected_networks", {}) plc_networks = plc_info.get("connected_networks", {})
markdown_lines.append("\n- **Networks:**") markdown_lines.append("\n- **Networks:**")
if not plc_networks: if not plc_networks:
@ -1530,7 +1749,7 @@ if __name__ == "__main__":
configs = {} configs = {}
working_directory = None working_directory = None
script_version = "v32.2 - Simplified IO Address Format (Separate Start/End)" # Updated version script_version = "v32.5 - Include PLC Local Modules in IO Summary" # Updated version
print( print(
f"--- AML (CAx Export) to Hierarchical JSON and Obsidian MD Converter ({script_version}) ---" f"--- AML (CAx Export) to Hierarchical JSON and Obsidian MD Converter ({script_version}) ---"
) )

View File

@ -172,8 +172,8 @@ def compare_excel_files(reference_excel_path, modified_excel_path):
# Detectar cambios # Detectar cambios
changes = {} changes = {}
columns_to_monitor = [ columns_to_monitor = [
'Device Address', 'IO Input Start Address', 'IO Input End Address', 'Device Address', 'IO Input Start Address', 'IO Input Word Count',
'IO Output Start Address', 'IO Output End Address', 'IO Output Start Address', 'IO Output Word Count',
'Network Type', 'Device Type', 'Order Number', 'Firmware Version' 'Network Type', 'Device Type', 'Order Number', 'Firmware Version'
] ]
@ -184,8 +184,26 @@ def compare_excel_files(reference_excel_path, modified_excel_path):
row_changes = {} row_changes = {}
for col in columns_to_monitor: for col in columns_to_monitor:
if col in df_ref.columns and col in df_mod.columns: if col in df_ref.columns and col in df_mod.columns:
ref_val = str(row_ref[col]).strip() if pd.notna(row_ref[col]) else 'N/A' # Manejo especial para valores numéricos
mod_val = str(row_mod[col]).strip() if pd.notna(row_mod[col]) else 'N/A' ref_raw = row_ref[col]
mod_raw = row_mod[col]
# Convertir a string, manejando floats apropiadamente
if pd.notna(ref_raw):
if isinstance(ref_raw, float) and ref_raw.is_integer():
ref_val = str(int(ref_raw))
else:
ref_val = str(ref_raw).strip()
else:
ref_val = 'N/A'
if pd.notna(mod_raw):
if isinstance(mod_raw, float) and mod_raw.is_integer():
mod_val = str(int(mod_raw))
else:
mod_val = str(mod_raw).strip()
else:
mod_val = 'N/A'
if ref_val != mod_val: if ref_val != mod_val:
row_changes[col] = { row_changes[col] = {
@ -203,6 +221,10 @@ def compare_excel_files(reference_excel_path, modified_excel_path):
print(f"Detectados {len(changes)} dispositivos con cambios.") print(f"Detectados {len(changes)} dispositivos con cambios.")
for unique_id, change_info in changes.items(): for unique_id, change_info in changes.items():
print(f" {unique_id}: {list(change_info['changes'].keys())}") print(f" {unique_id}: {list(change_info['changes'].keys())}")
# Debug: mostrar los primeros cambios para verificar valores
if len(changes) <= 5: # Solo mostrar detalles si son pocos cambios
for field, change_data in change_info['changes'].items():
print(f" {field}: {change_data['original']}{change_data['modified']}")
return True, changes return True, changes
@ -297,18 +319,28 @@ def apply_changes_to_aml(aml_tree, project_data, changes_dict):
# Encontrar el dispositivo en los datos del AML # Encontrar el dispositivo en los datos del AML
device_info = find_device_in_aml(project_data, plc_name, device_name) device_info = find_device_in_aml(project_data, plc_name, device_name)
if not device_info: if not device_info:
print(f" ERROR: No se pudo encontrar el dispositivo en el AML") print(f" ERROR: No se pudo encontrar el dispositivo '{device_name}' del PLC '{plc_name}' en el AML")
continue continue
print(f" Debug: Encontrado dispositivo - node_id: {device_info['node_id']}, display_id: {device_info['display_id']}")
# Encontrar el elemento XML correspondiente # Encontrar el elemento XML correspondiente
device_id = device_info['node_id'] node_id = device_info['node_id']
xml_element = root.xpath(f".//*[@ID='{device_id}']") display_id = device_info['display_id']
# Intentar primero con el display_id (el dispositivo real que contiene IOs)
xml_element = root.xpath(f".//*[@ID='{display_id}']")
if not xml_element: if not xml_element:
print(f" ERROR: No se pudo encontrar el elemento XML con ID {device_id}") # Si no se encuentra, intentar con el node_id
xml_element = root.xpath(f".//*[@ID='{node_id}']")
if not xml_element:
print(f" ERROR: No se pudo encontrar el elemento XML con ID {display_id} o {node_id}")
continue continue
device_element = xml_element[0] device_element = xml_element[0]
print(f" Debug: Usando elemento XML con ID {device_element.get('ID', 'N/A')}")
# Aplicar cambios específicos # Aplicar cambios específicos
for field, change_data in changes.items(): for field, change_data in changes.items():
@ -323,9 +355,9 @@ def apply_changes_to_aml(aml_tree, project_data, changes_dict):
else: else:
print(f" ✗ Error actualizando dirección de red") print(f" ✗ Error actualizando dirección de red")
elif field in ['IO Input Start Address', 'IO Input End Address', 'IO Output Start Address', 'IO Output End Address']: elif field in ['IO Input Start Address', 'IO Input Word Count', 'IO Output Start Address', 'IO Output Word Count']:
# Cambiar direcciones IO específicas # Cambiar direcciones IO específicas
if apply_io_address_change(device_element, field, original_val, modified_val, project_data, device_id): if apply_io_address_change(device_element, field, original_val, modified_val, project_data, display_id):
print(f"{field} actualizada: {original_val} -> {modified_val}") print(f"{field} actualizada: {original_val} -> {modified_val}")
changes_applied += 1 changes_applied += 1
else: else:
@ -348,19 +380,25 @@ def apply_network_address_change(device_element, new_address):
Aplica cambio de dirección de red a un elemento del dispositivo. Aplica cambio de dirección de red a un elemento del dispositivo.
""" """
try: try:
# Convertir new_address a string si es numérico
if isinstance(new_address, (int, float)):
address_str = str(new_address).rstrip('0').rstrip('.')
else:
address_str = str(new_address)
# Buscar atributo NetworkAddress # Buscar atributo NetworkAddress
addr_attr = device_element.xpath("./*[local-name()='Attribute'][@Name='NetworkAddress']") addr_attr = device_element.xpath("./*[local-name()='Attribute'][@Name='NetworkAddress']")
if addr_attr: if addr_attr:
value_elem = addr_attr[0].xpath("./*[local-name()='Value']") value_elem = addr_attr[0].xpath("./*[local-name()='Value']")
if value_elem: if value_elem:
value_elem[0].text = new_address value_elem[0].text = address_str
return True return True
# Si no existe, crear el atributo # Si no existe, crear el atributo
attr_elem = ET.SubElement(device_element, "Attribute") attr_elem = ET.SubElement(device_element, "Attribute")
attr_elem.set("Name", "NetworkAddress") attr_elem.set("Name", "NetworkAddress")
value_elem = ET.SubElement(attr_elem, "Value") value_elem = ET.SubElement(attr_elem, "Value")
value_elem.text = new_address value_elem.text = address_str
return True return True
except Exception as e: except Exception as e:
@ -406,102 +444,141 @@ def apply_device_attribute_change(device_element, field, new_value):
def apply_io_address_change(device_element, field, original_val, modified_val, project_data, device_id): def apply_io_address_change(device_element, field, original_val, modified_val, project_data, device_id):
""" """
Aplica cambios a las direcciones IO individuales. Ahora es más simple Aplica cambios a las direcciones IO usando Start Address + Word Count.
porque las direcciones están separadas en campos específicos. Esto es más simple y directo que calcular End Addresses.
""" """
try: try:
# Validar que el nuevo valor sea numérico o N/A # Validar que el nuevo valor sea numérico o N/A
if modified_val != 'N/A' and modified_val != '': if modified_val != 'N/A' and modified_val != '':
try: try:
new_addr_value = int(modified_val) # Manejar tanto floats como integers (pandas puede leer como float)
except ValueError: if isinstance(modified_val, str):
print(f" Error: Valor de dirección no válido: {modified_val}") # Si es string, intentar convertir
new_value = int(float(modified_val))
else:
# Si ya es numérico, convertir directamente
new_value = int(modified_val)
except (ValueError, TypeError):
print(f" Error: Valor no válido: {modified_val} (tipo: {type(modified_val)})")
return False return False
else: else:
# Si es N/A, no hay dirección IO para este tipo # Si es N/A, no hay dirección IO para este tipo
return True return True
# Determinar tipo de IO y si es start o end # Determinar tipo de IO y si es start address o word count
is_input = "Input" in field is_input = "Input" in field
is_start = "Start" in field is_start_address = "Start Address" in field
is_word_count = "Word Count" in field
io_type = "Input" if is_input else "Output" io_type = "Input" if is_input else "Output"
# Buscar la estructura IO en el dispositivo # Buscar la estructura IO en el dispositivo - con múltiples estrategias
# Esto requiere encontrar los elementos Address y sus sub-atributos print(f" Debug: Buscando elementos Address en dispositivo {device_element.get('Name', 'N/A')}")
# Estrategia 1: Buscar Address directamente en el elemento
address_elements = device_element.xpath("./*[local-name()='Attribute'][@Name='Address']") address_elements = device_element.xpath("./*[local-name()='Attribute'][@Name='Address']")
if not address_elements: if not address_elements:
print(f" No se encontró elemento Address en el dispositivo") # Estrategia 2: Buscar en elementos hijos
print(f" Debug: No se encontró Address directo, buscando en elementos hijos...")
address_elements = device_element.xpath(".//*[local-name()='Attribute'][@Name='Address']")
if not address_elements:
# Estrategia 3: Buscar en elementos padre (tal vez los IOs están en el parent)
print(f" Debug: No se encontró Address en hijos, buscando en elementos padre...")
parent_elements = device_element.xpath("parent::*")
if parent_elements:
address_elements = parent_elements[0].xpath(".//*[local-name()='Attribute'][@Name='Address']")
if not address_elements:
print(f" ERROR: No se encontró elemento Address en el dispositivo o sus alrededores")
print(f" Debug: Atributos disponibles en este elemento:")
for attr in device_element.xpath("./*[local-name()='Attribute']"):
attr_name = attr.get('Name', 'N/A')
print(f" - {attr_name}")
return False return False
# Buscar dentro del Address el sub-atributo correcto print(f" Debug: Encontrados {len(address_elements)} elemento(s) Address")
address_element = address_elements[0]
io_subelements = address_element.xpath(f"./*[local-name()='Attribute']")
# Buscar dentro del Address el sub-atributo correcto
target_io_element = None target_io_element = None
for io_sub in io_subelements:
# Verificar si es el tipo correcto (Input/Output) # Buscar en todos los elementos Address encontrados
type_val = io_sub.xpath("./*[local-name()='Attribute'][@Name='IoType']/*[local-name()='Value']/text()") for address_element in address_elements:
if type_val and type_val[0].lower() == io_type.lower(): print(f" Debug: Explorando Address element...")
target_io_element = io_sub io_subelements = address_element.xpath(f"./*[local-name()='Attribute']")
print(f" Debug: Encontrados {len(io_subelements)} sub-elementos en Address")
for io_sub in io_subelements:
sub_name = io_sub.get('Name', 'N/A')
print(f" Debug: Revisando sub-elemento: {sub_name}")
# Verificar si es el tipo correcto (Input/Output)
type_val = io_sub.xpath("./*[local-name()='Attribute'][@Name='IoType']/*[local-name()='Value']/text()")
if type_val:
found_type = type_val[0]
print(f" Debug: Encontrado IoType: {found_type}")
if found_type.lower() == io_type.lower():
target_io_element = io_sub
print(f" Debug: ¡Encontrado elemento {io_type} compatible!")
break
else:
# También revisar si el nombre del sub-elemento indica el tipo
if io_type.lower() in sub_name.lower():
print(f" Debug: Sub-elemento {sub_name} parece ser de tipo {io_type}")
target_io_element = io_sub
break
if target_io_element:
break break
if not target_io_element: if not target_io_element:
print(f" No se encontró elemento {io_type} en Address") print(f" ERROR: No se encontró elemento {io_type} en ningún Address")
print(f" Debug: Elementos Address disponibles:")
for addr_elem in address_elements:
sub_attrs = addr_elem.xpath(f"./*[local-name()='Attribute']")
for sub in sub_attrs:
sub_name = sub.get('Name', 'N/A')
type_vals = sub.xpath("./*[local-name()='Attribute'][@Name='IoType']/*[local-name()='Value']/text()")
type_info = type_vals[0] if type_vals else "No IoType"
print(f" - {sub_name} (IoType: {type_info})")
return False return False
# Actualizar StartAddress o calcular Length según el campo if is_start_address:
if is_start:
# Actualizar StartAddress # Actualizar StartAddress
start_attr = target_io_element.xpath("./*[local-name()='Attribute'][@Name='StartAddress']") start_attr = target_io_element.xpath("./*[local-name()='Attribute'][@Name='StartAddress']")
if start_attr: if start_attr:
value_elem = start_attr[0].xpath("./*[local-name()='Value']") value_elem = start_attr[0].xpath("./*[local-name()='Value']")
if value_elem: if value_elem:
value_elem[0].text = str(new_addr_value) value_elem[0].text = str(new_value)
return True return True
else: else:
# Crear StartAddress si no existe # Crear StartAddress si no existe
start_attr_elem = ET.SubElement(target_io_element, "Attribute") start_attr_elem = ET.SubElement(target_io_element, "Attribute")
start_attr_elem.set("Name", "StartAddress") start_attr_elem.set("Name", "StartAddress")
value_elem = ET.SubElement(start_attr_elem, "Value") value_elem = ET.SubElement(start_attr_elem, "Value")
value_elem.text = str(new_addr_value) value_elem.text = str(new_value)
return True return True
else:
# Es End Address - necesitamos calcular Length basándose en Start y End elif is_word_count:
# Primero obtener StartAddress # Actualizar Length basándose en Word Count
start_attr = target_io_element.xpath("./*[local-name()='Attribute'][@Name='StartAddress']/*[local-name()='Value']/text()") # Convertir words a bits (1 word = 16 bits)
if start_attr: length_bits = new_value * 16
try:
start_value = int(start_attr[0]) # Actualizar Length
# Length en bytes = (end - start + 1) length_attr = target_io_element.xpath("./*[local-name()='Attribute'][@Name='Length']")
length_bytes = new_addr_value - start_value + 1 if length_attr:
if length_bytes > 0: value_elem = length_attr[0].xpath("./*[local-name()='Value']")
# Convertir a bits (asumiendo 8 bits por byte) if value_elem:
length_bits = length_bytes * 8 value_elem[0].text = str(length_bits)
return True
# Actualizar Length
length_attr = target_io_element.xpath("./*[local-name()='Attribute'][@Name='Length']")
if length_attr:
value_elem = length_attr[0].xpath("./*[local-name()='Value']")
if value_elem:
value_elem[0].text = str(length_bits)
return True
else:
# Crear Length si no existe
length_attr_elem = ET.SubElement(target_io_element, "Attribute")
length_attr_elem.set("Name", "Length")
value_elem = ET.SubElement(length_attr_elem, "Value")
value_elem.text = str(length_bits)
return True
else:
print(f" Error: End address ({new_addr_value}) debe ser mayor que start address ({start_value})")
return False
except ValueError:
print(f" Error: No se pudo convertir start address: {start_attr[0]}")
return False
else: else:
print(f" Error: No se encontró StartAddress para calcular Length") # Crear Length si no existe
return False length_attr_elem = ET.SubElement(target_io_element, "Attribute")
length_attr_elem.set("Name", "Length")
value_elem = ET.SubElement(length_attr_elem, "Value")
value_elem.text = str(length_bits)
return True
return False return False
@ -544,7 +621,7 @@ def main():
configs = {} configs = {}
working_directory = None working_directory = None
script_version = "v1.1 - Simplified IO Address Handling (Start/End Separated)" script_version = "v1.4 - Enhanced Address Element Search with Debug"
print(f"--- Actualizador de AML desde Excel Modificado ({script_version}) ---") print(f"--- Actualizador de AML desde Excel Modificado ({script_version}) ---")
# Validar directorio de trabajo # Validar directorio de trabajo

View File

@ -0,0 +1,324 @@
"""
export_logic_from_tia : Script para exportar el software de un PLC desde TIA Portal en archivos XML y SCL.
"""
import tkinter as tk
from tkinter import filedialog
import os
import sys
import traceback
script_root = os.path.dirname(
os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
)
sys.path.append(script_root)
from backend.script_utils import load_configuration
# --- Configuration ---
TIA_PORTAL_VERSION = "18.0" # Target TIA Portal version (e.g., "18.0")
EXPORT_OPTIONS = None # Use default export options
KEEP_FOLDER_STRUCTURE = (
True # Replicate TIA project folder structure in export directory
)
# --- TIA Scripting Import Handling ---
# Check if the TIA_SCRIPTING environment variable is set
if os.getenv("TIA_SCRIPTING"):
sys.path.append(os.getenv("TIA_SCRIPTING"))
else:
# Optional: Define a fallback path if the environment variable isn't set
# fallback_path = "C:\\path\\to\\your\\TIA_Scripting_binaries"
# if os.path.exists(fallback_path):
# sys.path.append(fallback_path)
pass # Allow import to fail if not found
try:
import siemens_tia_scripting as ts
EXPORT_OPTIONS = (
ts.Enums.ExportOptions.WithDefaults
) # Set default options now that 'ts' is imported
except ImportError:
print("ERROR: Failed to import 'siemens_tia_scripting'.")
print("Ensure:")
print(f"1. TIA Portal Openness for V{TIA_PORTAL_VERSION} is installed.")
print(
"2. The 'siemens_tia_scripting' Python module is installed (pip install ...) or"
)
print(
" the path to its binaries is set in the 'TIA_SCRIPTING' environment variable."
)
print(
"3. You are using a compatible Python version (e.g., 3.12.X as per documentation)."
)
sys.exit(1)
except Exception as e:
print(f"An unexpected error occurred during import: {e}")
traceback.print_exc()
sys.exit(1)
# --- Functions ---
def select_project_file():
"""Opens a dialog to select a TIA Portal project file."""
root = tk.Tk()
root.withdraw() # Hide the main tkinter window
file_path = filedialog.askopenfilename(
title="Select TIA Portal Project File",
filetypes=[
(
f"TIA Portal V{TIA_PORTAL_VERSION} Projects",
f"*.ap{TIA_PORTAL_VERSION.split('.')[0]}",
)
], # e.g. *.ap18
)
root.destroy()
if not file_path:
print("No project file selected. Exiting.")
sys.exit(0)
return file_path
def select_export_directory():
"""Opens a dialog to select the export directory."""
root = tk.Tk()
root.withdraw() # Hide the main tkinter window
dir_path = filedialog.askdirectory(title="Select Export Directory")
root.destroy()
if not dir_path:
print("No export directory selected. Exiting.")
sys.exit(0)
return dir_path
def export_plc_data(plc, export_base_dir):
"""Exports Blocks, UDTs, and Tag Tables from a given PLC."""
plc_name = plc.get_name()
print(f"\n--- Processing PLC: {plc_name} ---")
# Define base export path for this PLC
plc_export_dir = os.path.join(export_base_dir, plc_name)
os.makedirs(plc_export_dir, exist_ok=True)
# --- Export Program Blocks ---
blocks_exported = 0
blocks_skipped = 0
print(f"\n[PLC: {plc_name}] Exporting Program Blocks...")
xml_blocks_path = os.path.join(plc_export_dir, "ProgramBlocks_XML")
scl_blocks_path = os.path.join(plc_export_dir, "ProgramBlocks_SCL")
os.makedirs(xml_blocks_path, exist_ok=True)
os.makedirs(scl_blocks_path, exist_ok=True)
print(f" XML Target: {xml_blocks_path}")
print(f" SCL Target: {scl_blocks_path}")
try:
program_blocks = plc.get_program_blocks() #
print(f" Found {len(program_blocks)} program blocks.")
for block in program_blocks:
block_name = block.get_name() # Assuming get_name() exists
print(f" Processing block: {block_name}...")
try:
if not block.is_consistent(): #
print(f" Compiling block {block_name}...")
block.compile() #
if not block.is_consistent():
print(
f" WARNING: Block {block_name} inconsistent after compile. Skipping."
)
blocks_skipped += 1
continue
print(f" Exporting {block_name} as XML...")
block.export(
target_directory_path=xml_blocks_path, #
export_options=EXPORT_OPTIONS, #
export_format=ts.Enums.ExportFormats.SimaticML, #
keep_folder_structure=KEEP_FOLDER_STRUCTURE,
) #
try:
prog_language = block.get_property(name="ProgrammingLanguage")
if prog_language == "SCL":
print(f" Exporting {block_name} as SCL...")
block.export(
target_directory_path=scl_blocks_path,
export_options=EXPORT_OPTIONS,
export_format=ts.Enums.ExportFormats.ExternalSource, #
keep_folder_structure=KEEP_FOLDER_STRUCTURE,
)
except Exception as prop_ex:
print(
f" Could not get ProgrammingLanguage for {block_name}. Skipping SCL. Error: {prop_ex}"
)
blocks_exported += 1
except Exception as block_ex:
print(f" ERROR exporting block {block_name}: {block_ex}")
blocks_skipped += 1
print(
f" Program Blocks Export Summary: Exported={blocks_exported}, Skipped/Errors={blocks_skipped}"
)
except Exception as e:
print(f" ERROR processing Program Blocks: {e}")
traceback.print_exc()
# --- Export PLC Data Types (UDTs) ---
udts_exported = 0
udts_skipped = 0
print(f"\n[PLC: {plc_name}] Exporting PLC Data Types (UDTs)...")
udt_export_path = os.path.join(plc_export_dir, "PlcDataTypes")
os.makedirs(udt_export_path, exist_ok=True)
print(f" Target: {udt_export_path}")
try:
udts = plc.get_user_data_types() #
print(f" Found {len(udts)} UDTs.")
for udt in udts:
udt_name = udt.get_name() #
print(f" Processing UDT: {udt_name}...")
try:
if not udt.is_consistent(): #
print(f" Compiling UDT {udt_name}...")
udt.compile() #
if not udt.is_consistent():
print(
f" WARNING: UDT {udt_name} inconsistent after compile. Skipping."
)
udts_skipped += 1
continue
print(f" Exporting {udt_name}...")
udt.export(
target_directory_path=udt_export_path, #
export_options=EXPORT_OPTIONS, #
# export_format defaults to SimaticML for UDTs
keep_folder_structure=KEEP_FOLDER_STRUCTURE,
) #
udts_exported += 1
except Exception as udt_ex:
print(f" ERROR exporting UDT {udt_name}: {udt_ex}")
udts_skipped += 1
print(
f" UDT Export Summary: Exported={udts_exported}, Skipped/Errors={udts_skipped}"
)
except Exception as e:
print(f" ERROR processing UDTs: {e}")
traceback.print_exc()
# --- Export PLC Tag Tables ---
tags_exported = 0
tags_skipped = 0
print(f"\n[PLC: {plc_name}] Exporting PLC Tag Tables...")
tags_export_path = os.path.join(plc_export_dir, "PlcTags")
os.makedirs(tags_export_path, exist_ok=True)
print(f" Target: {tags_export_path}")
try:
tag_tables = plc.get_plc_tag_tables() #
print(f" Found {len(tag_tables)} Tag Tables.")
for table in tag_tables:
table_name = table.get_name() #
print(f" Processing Tag Table: {table_name}...")
try:
# Note: Consistency check might not be available/needed for tag tables like blocks/UDTs
print(f" Exporting {table_name}...")
table.export(
target_directory_path=tags_export_path, #
export_options=EXPORT_OPTIONS, #
# export_format defaults to SimaticML for Tag Tables
keep_folder_structure=KEEP_FOLDER_STRUCTURE,
) #
tags_exported += 1
except Exception as table_ex:
print(f" ERROR exporting Tag Table {table_name}: {table_ex}")
tags_skipped += 1
print(
f" Tag Table Export Summary: Exported={tags_exported}, Skipped/Errors={tags_skipped}"
)
except Exception as e:
print(f" ERROR processing Tag Tables: {e}")
traceback.print_exc()
print(f"\n--- Finished processing PLC: {plc_name} ---")
# --- Main Script ---
if __name__ == "__main__":
configs = load_configuration()
working_directory = configs.get("working_directory")
print("--- TIA Portal Data Exporter (Blocks, UDTs, Tags) ---")
# Validate working directory
if not working_directory or not os.path.isdir(working_directory):
print("ERROR: Working directory not set or invalid in configuration.")
print("Please configure the working directory using the main application.")
sys.exit(1)
# 1. Select Project File, Export Directory comes from config
project_file = select_project_file()
export_dir = working_directory # Use working directory from config
print(f"\nSelected Project: {project_file}")
print(f"Using Export Directory (Working Directory): {export_dir}")
portal_instance = None
project_object = None
try:
# 2. Connect to TIA Portal
print(f"\nConnecting to TIA Portal V{TIA_PORTAL_VERSION}...")
portal_instance = ts.open_portal(
version=TIA_PORTAL_VERSION,
portal_mode=ts.Enums.PortalMode.WithGraphicalUserInterface,
)
print("Connected to TIA Portal.")
print(f"Portal Process ID: {portal_instance.get_process_id()}") #
# 3. Open Project
print(f"Opening project: {os.path.basename(project_file)}...")
project_object = portal_instance.open_project(project_file_path=project_file) #
if project_object is None:
print("Project might already be open, attempting to get handle...")
project_object = portal_instance.get_project() #
if project_object is None:
raise Exception("Failed to open or get the specified project.")
print("Project opened successfully.")
# 4. Get PLCs
plcs = project_object.get_plcs() #
if not plcs:
print("No PLC devices found in the project.")
else:
print(f"Found {len(plcs)} PLC(s). Starting export process...")
# 5. Iterate and Export Data for each PLC
for plc_device in plcs:
export_plc_data(
plc=plc_device, export_base_dir=export_dir
) # Pass export_dir
print("\nExport process completed.")
except ts.TiaException as tia_ex:
print(f"\nTIA Portal Openness Error: {tia_ex}")
traceback.print_exc()
except FileNotFoundError:
print(f"\nERROR: Project file not found at {project_file}")
except Exception as e:
print(f"\nAn unexpected error occurred: {e}")
traceback.print_exc()
finally:
# 6. Cleanup
if portal_instance:
try:
print("\nClosing TIA Portal...")
portal_instance.close_portal() #
print("TIA Portal closed.")
except Exception as close_ex:
print(f"Error during TIA Portal cleanup: {close_ex}")
print("\nScript finished.")

View File

@ -0,0 +1,371 @@
"""
export_CAx_from_tia : Script que exporta los datos CAx de un proyecto de TIA Portal y genera un resumen en Markdown.
"""
import tkinter as tk
from tkinter import filedialog
import os
import sys
import traceback
import xml.etree.ElementTree as ET # Library to parse XML (AML)
from pathlib import Path # Import Path
script_root = os.path.dirname(
os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
)
sys.path.append(script_root)
from backend.script_utils import load_configuration
# --- Configuration ---
# Supported TIA Portal versions mapping (extension -> version)
SUPPORTED_TIA_VERSIONS = {
".ap18": "18.0",
".ap19": "19.0",
".ap20": "20.0"
}
# --- TIA Scripting Import Handling ---
# (Same import handling as the previous script)
if os.getenv("TIA_SCRIPTING"):
sys.path.append(os.getenv("TIA_SCRIPTING"))
else:
pass
try:
import siemens_tia_scripting as ts
except ImportError:
print("ERROR: Failed to import 'siemens_tia_scripting'.")
print("Ensure TIA Openness, the module, and Python 3.12.X are set up.")
sys.exit(1)
except Exception as e:
print(f"An unexpected error occurred during import: {e}")
traceback.print_exc()
sys.exit(1)
# --- Functions ---
def get_supported_filetypes():
"""Returns the supported file types for TIA Portal projects."""
filetypes = []
for ext, version in SUPPORTED_TIA_VERSIONS.items():
version_major = version.split('.')[0]
filetypes.append((f"TIA Portal V{version_major} Projects", f"*{ext}"))
# Add option to show all supported files
all_extensions = " ".join([f"*{ext}" for ext in SUPPORTED_TIA_VERSIONS.keys()])
filetypes.insert(0, ("All TIA Portal Projects", all_extensions))
return filetypes
def detect_tia_version(project_file_path):
"""Detects TIA Portal version based on file extension."""
file_path = Path(project_file_path)
file_extension = file_path.suffix.lower()
if file_extension in SUPPORTED_TIA_VERSIONS:
detected_version = SUPPORTED_TIA_VERSIONS[file_extension]
print(f"Detected TIA Portal version: {detected_version} (from extension {file_extension})")
return detected_version
else:
print(f"WARNING: Unrecognized file extension '{file_extension}'. Supported extensions: {list(SUPPORTED_TIA_VERSIONS.keys())}")
# Default to version 18.0 for backward compatibility
print("Defaulting to TIA Portal V18.0")
return "18.0"
def select_project_file():
"""Opens a dialog to select a TIA Portal project file."""
root = tk.Tk()
root.withdraw()
file_path = filedialog.askopenfilename(
title="Select TIA Portal Project File",
filetypes=get_supported_filetypes(),
)
root.destroy()
if not file_path:
print("No project file selected. Exiting.")
sys.exit(0)
return file_path
def select_output_directory():
"""Opens a dialog to select the output directory."""
root = tk.Tk()
root.withdraw()
dir_path = filedialog.askdirectory(
title="Select Output Directory for AML and MD files"
)
root.destroy()
if not dir_path:
print("No output directory selected. Exiting.")
sys.exit(0)
return dir_path
def find_elements(element, path):
"""Helper to find elements using namespaces commonly found in AML."""
# AutomationML namespaces often vary slightly or might be default
# This basic approach tries common prefixes or no prefix
namespaces = {
"": (
element.tag.split("}")[0][1:] if "}" in element.tag else ""
), # Default namespace if present
"caex": "http://www.dke.de/CAEX", # Common CAEX namespace
# Add other potential namespaces if needed based on file inspection
}
# Try finding with common prefixes or the default namespace
for prefix, uri in namespaces.items():
# Construct path with namespace URI if prefix is defined
namespaced_path = path
if prefix:
parts = path.split("/")
namespaced_parts = [
f"{{{uri}}}{part}" if part != "." else part for part in parts
]
namespaced_path = "/".join(namespaced_parts)
# Try findall with the constructed path
found = element.findall(namespaced_path)
if found:
return found # Return first successful find
# Fallback: try finding without explicit namespace (might work if default ns is used throughout)
# This might require adjusting the path string itself depending on the XML structure
try:
# Simple attempt without namespace handling if the above fails
return element.findall(path)
except (
SyntaxError
): # Handle potential errors if path isn't valid without namespaces
return []
def parse_aml_to_markdown(aml_file_path, md_file_path):
"""Parses the AML file and generates a Markdown summary."""
print(f"Parsing AML file: {aml_file_path}")
try:
tree = ET.parse(aml_file_path)
root = tree.getroot()
markdown_lines = ["# Project CAx Data Summary (AutomationML)", ""]
# Find InstanceHierarchy - usually contains the project structure
# Note: Namespace handling in ElementTree can be tricky. Adjust '{...}' part if needed.
# We will use a helper function 'find_elements' to try common patterns
instance_hierarchies = find_elements(
root, ".//InstanceHierarchy"
) # Common CAEX tag
if not instance_hierarchies:
markdown_lines.append("Could not find InstanceHierarchy in the AML file.")
print("Warning: Could not find InstanceHierarchy element.")
else:
# Assuming the first InstanceHierarchy is the main one
ih = instance_hierarchies[0]
markdown_lines.append(f"## Instance Hierarchy: {ih.get('Name', 'N/A')}")
markdown_lines.append("")
# Look for InternalElements which represent devices/components
internal_elements = find_elements(
ih, ".//InternalElement"
) # Common CAEX tag
if not internal_elements:
markdown_lines.append(
"No devices (InternalElement) found in InstanceHierarchy."
)
print("Info: No InternalElement tags found under InstanceHierarchy.")
else:
markdown_lines.append(
f"Found {len(internal_elements)} device(s)/component(s):"
)
markdown_lines.append("")
markdown_lines.append(
"| Name | SystemUnitClass | RefBaseSystemUnitPath | Attributes |"
)
markdown_lines.append("|---|---|---|---|")
for elem in internal_elements:
name = elem.get("Name", "N/A")
ref_path = elem.get(
"RefBaseSystemUnitPath", "N/A"
) # Path to class definition
# Try to get the class name from the RefBaseSystemUnitPath or SystemUnitClassLib
su_class_path = find_elements(
elem, ".//SystemUnitClass"
) # Check direct child first
su_class = (
su_class_path[0].get("Path", "N/A")
if su_class_path
else ref_path.split("/")[-1]
) # Fallback to last part of path
attributes_md = ""
attributes = find_elements(elem, ".//Attribute") # Find attributes
attr_list = []
for attr in attributes:
attr_name = attr.get("Name", "")
attr_value_elem = find_elements(
attr, ".//Value"
) # Get Value element
attr_value = (
attr_value_elem[0].text
if attr_value_elem and attr_value_elem[0].text
else "N/A"
)
# Look for potential IP addresses (common attribute names)
if "Address" in attr_name or "IP" in attr_name:
attr_list.append(f"**{attr_name}**: {attr_value}")
else:
attr_list.append(f"{attr_name}: {attr_value}")
attributes_md = "<br>".join(attr_list) if attr_list else "None"
markdown_lines.append(
f"| {name} | {su_class} | `{ref_path}` | {attributes_md} |"
)
# Write to Markdown file
with open(md_file_path, "w", encoding="utf-8") as f:
f.write("\n".join(markdown_lines))
print(f"Markdown summary written to: {md_file_path}")
except ET.ParseError as xml_err:
print(f"ERROR parsing XML file {aml_file_path}: {xml_err}")
with open(md_file_path, "w", encoding="utf-8") as f:
f.write(
f"# Error\n\nFailed to parse AML file: {os.path.basename(aml_file_path)}\n\nError: {xml_err}"
)
except Exception as e:
print(f"ERROR processing AML file {aml_file_path}: {e}")
traceback.print_exc()
with open(md_file_path, "w", encoding="utf-8") as f:
f.write(
f"# Error\n\nAn unexpected error occurred while processing AML file: {os.path.basename(aml_file_path)}\n\nError: {e}"
)
# --- Main Script ---
if __name__ == "__main__":
configs = load_configuration()
working_directory = configs.get("working_directory")
print("--- TIA Portal Project CAx Exporter and Analyzer ---")
# Validate working directory
if not working_directory or not os.path.isdir(working_directory):
print("ERROR: Working directory not set or invalid in configuration.")
print("Please configure the working directory using the main application.")
sys.exit(1)
# 1. Select Project File, Output Directory comes from config
project_file = select_project_file()
output_dir = Path(
working_directory
) # Use working directory from config, ensure it's a Path object
print(f"\nSelected Project: {project_file}")
print(f"Using Output Directory (Working Directory): {output_dir}")
# 2. Detect TIA Portal version from project file
tia_version = detect_tia_version(project_file)
# Define output file names using Path object
project_path = Path(project_file)
project_base_name = project_path.stem # Get filename without extension
aml_file = output_dir / f"{project_base_name}_CAx_Export.aml"
md_file = output_dir / f"{project_base_name}_CAx_Summary.md"
log_file = (
output_dir / f"{project_base_name}_CAx_Export.log"
) # Log file for the export process
print(f"Will export CAx data to: {aml_file}")
print(f"Will generate summary to: {md_file}")
print(f"Export log file: {log_file}")
portal_instance = None
project_object = None
cax_export_successful = False
try:
# 3. Connect to TIA Portal with detected version
print(f"\nConnecting to TIA Portal V{tia_version}...")
portal_instance = ts.open_portal(
version=tia_version,
portal_mode=ts.Enums.PortalMode.WithGraphicalUserInterface,
)
print("Connected.")
# 4. Open Project
print(
f"Opening project: {project_path.name}..."
) # Use Path object's name attribute
project_object = portal_instance.open_project(
project_file_path=str(project_path)
) # Pass path as string
if project_object is None:
print("Project might already be open, attempting to get handle...")
project_object = portal_instance.get_project()
if project_object is None:
raise Exception("Failed to open or get the specified project.")
print("Project opened.")
# 5. Export CAx Data (Project Level)
print(f"Exporting CAx data for the project to {aml_file}...")
# Ensure output directory exists (Path.mkdir handles this implicitly if needed later, but good practice)
output_dir.mkdir(parents=True, exist_ok=True)
# Pass paths as strings to the TIA function
export_result = project_object.export_cax_data(
export_file_path=str(aml_file), log_file_path=str(log_file)
)
if export_result:
print("CAx data exported successfully.")
cax_export_successful = True
else:
print("CAx data export failed. Check the log file for details:")
print(f" Log file: {log_file}")
# Write basic error message to MD file if export fails
with open(md_file, "w", encoding="utf-8") as f:
f.write(
f"# Error\n\nCAx data export failed. Check log file: {log_file}"
)
except ts.TiaException as tia_ex:
print(f"\nTIA Portal Openness Error: {tia_ex}")
traceback.print_exc()
except FileNotFoundError:
print(f"\nERROR: Project file not found at {project_file}")
except Exception as e:
print(f"\nAn unexpected error occurred during TIA interaction: {e}")
traceback.print_exc()
finally:
# Close TIA Portal before processing the file (or detach)
if portal_instance:
try:
print("\nClosing TIA Portal...")
portal_instance.close_portal()
print("TIA Portal closed.")
except Exception as close_ex:
print(f"Error during TIA Portal cleanup: {close_ex}")
# 6. Parse AML and Generate Markdown (only if export was successful)
if cax_export_successful:
if aml_file.exists(): # Use Path object's exists() method
parse_aml_to_markdown(aml_file, md_file)
else:
print(
f"ERROR: Export was reported successful, but AML file not found at {aml_file}"
)
with open(md_file, "w", encoding="utf-8") as f:
f.write(
f"# Error\n\nExport was reported successful, but AML file not found:\n{aml_file}"
)
print("\nScript finished.")

File diff suppressed because it is too large Load Diff

View File

@ -5,5 +5,5 @@
}, },
"level2": {}, "level2": {},
"level3": {}, "level3": {},
"working_directory": "C:\\Trabajo\\SIDEL\\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\\Reporte\\SourceDoc\\SourcdSD" "working_directory": "D:\\Trabajo\\VM\\44 - 98050 - Fiera\\Reporte\\ExportsTia\\Source"
} }

View File

@ -1,6 +1,7 @@
{ {
"path": "C:\\Trabajo\\SIDEL\\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\\Reporte\\SourceDoc\\SourcdSD", "path": "D:\\Trabajo\\VM\\44 - 98050 - Fiera\\Reporte\\ExportsTia\\Source",
"history": [ "history": [
"D:\\Trabajo\\VM\\44 - 98050 - Fiera\\Reporte\\ExportsTia\\Source",
"C:\\Trabajo\\SIDEL\\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\\Reporte\\SourceDoc\\SourcdSD", "C:\\Trabajo\\SIDEL\\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\\Reporte\\SourceDoc\\SourcdSD",
"C:\\Trabajo\\SIDEL\\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\\Reporte\\SourceDoc\\SourceXML" "C:\\Trabajo\\SIDEL\\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\\Reporte\\SourceDoc\\SourceXML"
] ]

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -15,5 +15,5 @@
"xref_source_subdir": "source" "xref_source_subdir": "source"
}, },
"level3": {}, "level3": {},
"working_directory": "C:\\Trabajo\\SIDEL\\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\\Reporte\\SourceDoc\\SourceXML" "working_directory": "D:\\Trabajo\\VM\\44 - 98050 - Fiera\\Reporte\\ExportsTia\\Source"
} }

View File

@ -1,6 +1,7 @@
{ {
"path": "C:\\Trabajo\\SIDEL\\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\\Reporte\\SourceDoc\\SourceXML", "path": "D:\\Trabajo\\VM\\44 - 98050 - Fiera\\Reporte\\ExportsTia\\Source",
"history": [ "history": [
"D:\\Trabajo\\VM\\44 - 98050 - Fiera\\Reporte\\ExportsTia\\Source",
"C:\\Trabajo\\SIDEL\\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\\Reporte\\SourceDoc\\SourceXML", "C:\\Trabajo\\SIDEL\\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\\Reporte\\SourceDoc\\SourceXML",
"C:\\Trabajo\\SIDEL\\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\\Reporte\\IOExport" "C:\\Trabajo\\SIDEL\\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\\Reporte\\IOExport"
] ]

View File

@ -1,5 +1,213 @@
{ {
"history": [ "history": [
{
"id": "a599effd",
"group_id": "4",
"script_name": "x1.py",
"executed_date": "2025-06-12T19:59:02.082348Z",
"arguments": [],
"working_directory": "D:/Proyectos/Scripts/Siemens/Tia Portal Utils",
"python_env": "tia_scripting",
"executable_type": "pythonw.exe",
"status": "running",
"pid": 36824,
"execution_time": null
},
{
"id": "d722b721",
"group_id": "4",
"script_name": "x1.py",
"executed_date": "2025-06-12T09:27:11.172727Z",
"arguments": [],
"working_directory": "D:/Proyectos/Scripts/Siemens/Tia Portal Utils",
"python_env": "tia_scripting",
"executable_type": "pythonw.exe",
"status": "running",
"pid": 24964,
"execution_time": null
},
{
"id": "0a84ec87",
"group_id": "1",
"script_name": "calc.py",
"executed_date": "2025-06-12T09:24:00.527248Z",
"arguments": [],
"working_directory": "D:/Proyectos/Scripts/Calcv2",
"python_env": "tia_scripting",
"executable_type": "pythonw.exe",
"status": "running",
"pid": 15548,
"execution_time": null
},
{
"id": "6297e689",
"group_id": "4",
"script_name": "x1.py",
"executed_date": "2025-06-12T09:16:08.148925Z",
"arguments": [],
"working_directory": "D:/Proyectos/Scripts/Siemens/Tia Portal Utils",
"python_env": "tia_scripting",
"executable_type": "pythonw.exe",
"status": "running",
"pid": 16892,
"execution_time": null
},
{
"id": "79f5a9e6",
"group_id": "4",
"script_name": "x1.py",
"executed_date": "2025-06-12T09:07:06.392550Z",
"arguments": [],
"working_directory": "D:/Proyectos/Scripts/Siemens/Tia Portal Utils",
"python_env": "tia_scripting",
"executable_type": "pythonw.exe",
"status": "running",
"pid": 45652,
"execution_time": null
},
{
"id": "22e264cd",
"group_id": "4",
"script_name": "x1.py",
"executed_date": "2025-06-12T09:07:06.280903Z",
"arguments": [],
"working_directory": "D:/Proyectos/Scripts/Siemens/Tia Portal Utils",
"python_env": "tia_scripting",
"executable_type": "pythonw.exe",
"status": "running",
"pid": 25984,
"execution_time": null
},
{
"id": "589be793",
"group_id": "4",
"script_name": "x1.py",
"executed_date": "2025-06-11T23:17:19.621521Z",
"arguments": [],
"working_directory": "D:/Proyectos/Scripts/Siemens/Tia Portal Utils",
"python_env": "tia_scripting",
"executable_type": "python.exe",
"status": "success",
"pid": 25652,
"execution_time": 389.689278
},
{
"id": "6ca3fbde",
"group_id": "4",
"script_name": "x1.py",
"executed_date": "2025-06-11T23:17:06.940558Z",
"arguments": [],
"working_directory": "D:/Proyectos/Scripts/Siemens/Tia Portal Utils",
"python_env": "tia_scripting",
"executable_type": "pythonw.exe",
"status": "running",
"pid": 29496,
"execution_time": null
},
{
"id": "6f646ca2",
"group_id": "4",
"script_name": "x1.py",
"executed_date": "2025-06-11T23:16:59.013936Z",
"arguments": [],
"working_directory": "D:/Proyectos/Scripts/Siemens/Tia Portal Utils",
"python_env": "tia_scripting",
"executable_type": "python.exe",
"status": "success",
"pid": 38516,
"execution_time": 3.471176
},
{
"id": "88e35e8b",
"group_id": "2",
"script_name": "main.py",
"executed_date": "2025-06-11T14:00:34.786377Z",
"arguments": [],
"working_directory": "D:/Proyectos/Scripts/RS485/MaselliSimulatorApp",
"python_env": "tia_scripting",
"executable_type": "pythonw.exe",
"status": "running",
"pid": 404,
"execution_time": null
},
{
"id": "b373c77d",
"group_id": "2",
"script_name": "main.py",
"executed_date": "2025-06-11T13:51:11.750064Z",
"arguments": [],
"working_directory": "D:/Proyectos/Scripts/RS485/MaselliSimulatorApp",
"python_env": "tia_scripting",
"executable_type": "pythonw.exe",
"status": "running",
"pid": 24472,
"execution_time": null
},
{
"id": "455a8271",
"group_id": "2",
"script_name": "main.py",
"executed_date": "2025-06-10T15:45:56.713179Z",
"arguments": [],
"working_directory": "D:/Proyectos/Scripts/RS485/MaselliSimulatorApp",
"python_env": "tia_scripting",
"executable_type": "pythonw.exe",
"status": "running",
"pid": 16480,
"execution_time": null
},
{
"id": "a8b813b0",
"group_id": "2",
"script_name": "main.py",
"executed_date": "2025-06-10T15:40:39.728099Z",
"arguments": [],
"working_directory": "D:/Proyectos/Scripts/RS485/MaselliSimulatorApp",
"python_env": "tia_scripting",
"executable_type": "pythonw.exe",
"status": "running",
"pid": 36640,
"execution_time": null
},
{
"id": "7924a173",
"group_id": "2",
"script_name": "main.py",
"executed_date": "2025-06-10T15:36:55.303738Z",
"arguments": [],
"working_directory": "D:/Proyectos/Scripts/RS485/MaselliSimulatorApp",
"python_env": "tia_scripting",
"executable_type": "pythonw.exe",
"status": "running",
"pid": 15144,
"execution_time": null
},
{
"id": "c3796f4e",
"group_id": "2",
"script_name": "main.py",
"executed_date": "2025-06-10T15:36:18.822898Z",
"arguments": [],
"working_directory": "D:/Proyectos/Scripts/RS485/MaselliSimulatorApp",
"python_env": "tia_scripting",
"executable_type": "pythonw.exe",
"status": "running",
"pid": 30656,
"execution_time": null
},
{
"id": "1040f2b6",
"group_id": "2",
"script_name": "main.py",
"executed_date": "2025-06-10T11:03:59.233659Z",
"arguments": [],
"working_directory": "D:/Proyectos/Scripts/RS485/MaselliSimulatorApp",
"python_env": "tia_scripting",
"executable_type": "pythonw.exe",
"status": "running",
"pid": 26152,
"execution_time": null
},
{ {
"id": "6d4e8908", "id": "6d4e8908",
"group_id": "1", "group_id": "1",

File diff suppressed because one or more lines are too long

View File

@ -33,6 +33,19 @@
"directory": "D:/Proyectos/Scripts/HMI Translate", "directory": "D:/Proyectos/Scripts/HMI Translate",
"created_date": "2025-06-03T12:31:19.529046Z", "created_date": "2025-06-03T12:31:19.529046Z",
"updated_date": "2025-06-03T12:44:24.651659Z" "updated_date": "2025-06-03T12:44:24.651659Z"
},
{
"id": "4",
"name": "Tia Portal Utils",
"description": "",
"category": "Otros",
"version": "1.0",
"python_env": "tia_scripting",
"directory": "D:/Proyectos/Scripts/Siemens/Tia Portal Utils",
"author": "",
"tags": [],
"created_date": "2025-06-11T22:55:41.635081Z",
"updated_date": "2025-06-11T22:55:41.635081Z"
} }
], ],
"categories": { "categories": {

View File

@ -1,5 +1,5 @@
[15:36:49] Iniciando ejecución de x7_update_CAx.py en D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia... [20:03:36] Iniciando ejecución de x1.py en D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\Source...
[15:36:49] --- Actualizador de AML desde Excel Modificado (v1.1 - Simplified IO Address Handling (Start/End Separated)) --- [20:03:37] --- TIA Portal Data Exporter (Blocks, UDTs, Tags) ---
[15:36:49] Directorio de trabajo: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia [20:03:47] No project file selected. Exiting.
[15:36:49] 1. Seleccione el archivo AML original: [20:03:47] Ejecución de x1.py finalizada (success). Duración: 0:00:10.497365.
[15:36:57] 2. Seleccione el archivo Excel modificado: [20:03:47] Log completo guardado en: D:\Proyectos\Scripts\ParamManagerScripts\backend\script_groups\ObtainIOFromProjectTia\log_x1.txt

View File

@ -248,15 +248,24 @@ class LauncherManager {
} }
} }
// Función actualizada para mostrar/ocultar ambos botones de editor // Función actualizada para mostrar/ocultar todos los botones
updateEditorButtons(show) { updateEditorButtons(show) {
const vscodeButton = document.getElementById('vscode-launcher-btn'); const vscodeButton = document.getElementById('vscode-launcher-btn');
const cursorButton = document.getElementById('cursor-launcher-btn'); const cursorButton = document.getElementById('cursor-launcher-btn');
const folderButton = document.getElementById('folder-launcher-btn');
const copyPathButton = document.getElementById('copy-path-launcher-btn');
if (vscodeButton) { if (vscodeButton) {
vscodeButton.style.display = show ? 'inline-flex' : 'none'; // Usar inline-flex para iconos+texto si aplica vscodeButton.style.display = show ? 'block' : 'none';
} }
if (cursorButton) { if (cursorButton) {
cursorButton.style.display = show ? 'inline-flex' : 'none'; // Usar inline-flex para iconos+texto si aplica cursorButton.style.display = show ? 'block' : 'none';
}
if (folderButton) {
folderButton.style.display = show ? 'block' : 'none';
}
if (copyPathButton) {
copyPathButton.style.display = show ? 'block' : 'none';
} }
} }
@ -1101,8 +1110,7 @@ class LauncherManager {
} }
async clearLauncherHistory() { async clearLauncherHistory() {
if (!confirm('¿Estás seguro de que quieres limpiar el historial?')) return; // Eliminada la confirmación - directamente procede a limpiar
try { try {
const response = await fetch('/api/launcher-history', { const response = await fetch('/api/launcher-history', {
method: 'DELETE' method: 'DELETE'
@ -1112,6 +1120,7 @@ class LauncherManager {
if (result.status === 'success') { if (result.status === 'success') {
this.history = []; this.history = [];
this.renderHistory(); this.renderHistory();
console.log('Historial del launcher limpiado');
} }
} catch (error) { } catch (error) {
console.error('Error clearing history:', error); console.error('Error clearing history:', error);
@ -1251,8 +1260,7 @@ class LauncherManager {
} }
async terminateProcess(pid) { async terminateProcess(pid) {
if (!confirm('¿Estás seguro de que quieres cerrar este proceso?')) return; // Eliminada la confirmación - directamente procede a cerrar
try { try {
const response = await fetch(`/api/launcher-process-terminate/${pid}`, { const response = await fetch(`/api/launcher-process-terminate/${pid}`, {
method: 'POST' method: 'POST'
@ -1271,6 +1279,73 @@ class LauncherManager {
alert('Error cerrando proceso'); alert('Error cerrando proceso');
} }
} }
// Nueva función para abrir carpeta del grupo launcher
async openGroupFolder() {
if (!this.currentGroup) {
alert('Selecciona un grupo primero');
return;
}
try {
const response = await fetch(`/api/open-group-folder/launcher/${this.currentGroup.id}`, {
method: 'POST'
});
const result = await response.json();
if (result.status === 'success') {
console.log(`Carpeta abierta: ${result.path}`);
} else {
alert(`Error al abrir carpeta: ${result.message}`);
}
} catch (error) {
console.error('Error opening folder:', error);
alert('Error al comunicarse con el servidor');
}
}
// Nueva función para copiar path del grupo launcher
async copyGroupPath() {
if (!this.currentGroup) {
alert('Selecciona un grupo primero');
return;
}
try {
const response = await fetch(`/api/get-group-path/launcher/${this.currentGroup.id}`);
const result = await response.json();
if (result.status === 'success') {
// Copiar al portapapeles usando la API moderna
if (navigator.clipboard && navigator.clipboard.writeText) {
await navigator.clipboard.writeText(result.path);
console.log('Path copiado al portapapeles');
} else {
// Fallback para navegadores más antiguos
const textArea = document.createElement('textarea');
textArea.value = result.path;
document.body.appendChild(textArea);
textArea.focus();
textArea.select();
try {
document.execCommand('copy');
console.log('Path copiado al portapapeles');
} catch (err) {
console.error('Error copying to clipboard:', err);
alert(`Error al copiar. Path: ${result.path}`);
}
document.body.removeChild(textArea);
}
} else {
alert(`Error al obtener path: ${result.message}`);
}
} catch (error) {
console.error('Error getting path:', error);
alert('Error al comunicarse con el servidor');
}
}
} }
// === FUNCIONES GLOBALES === // === FUNCIONES GLOBALES ===
@ -1416,3 +1491,21 @@ function closeMarkdownViewer() {
document.addEventListener('DOMContentLoaded', function () { document.addEventListener('DOMContentLoaded', function () {
console.log('Launcher JS loaded'); console.log('Launcher JS loaded');
}); });
// Función para colapsar/expandir el historial
function toggleHistoryPanel() {
const historyList = document.getElementById('history-list');
const toggleIcon = document.getElementById('history-toggle-icon');
if (historyList && toggleIcon) {
if (historyList.classList.contains('hidden')) {
// Expandir
historyList.classList.remove('hidden');
toggleIcon.textContent = '▼';
} else {
// Colapsar
historyList.classList.add('hidden');
toggleIcon.textContent = '▶';
}
}
}

View File

@ -45,7 +45,7 @@ function initWebSocket() {
async function loadConfigs() { async function loadConfigs() {
const group = currentGroup; const group = currentGroup;
console.log('Loading configs for group:', group); console.log('Loading configs for group:', group);
if (!group) { if (!group) {
console.error('No group selected'); console.error('No group selected');
return; return;
@ -61,11 +61,11 @@ async function loadConfigs() {
console.log(`Level ${level} data:`, data); console.log(`Level ${level} data:`, data);
await renderForm(`level${level}-form`, data); await renderForm(`level${level}-form`, data);
} }
// Cargar nivel 3 solo si hay directorio de trabajo // Cargar nivel 3 solo si hay directorio de trabajo
const workingDirResponse = await fetch(`/api/working-directory/${group}`); const workingDirResponse = await fetch(`/api/working-directory/${group}`);
const workingDirResult = await workingDirResponse.json(); const workingDirResult = await workingDirResponse.json();
if (workingDirResult.status === 'success' && workingDirResult.path) { if (workingDirResult.status === 'success' && workingDirResult.path) {
console.log('Loading level 3 config...'); console.log('Loading level 3 config...');
const response = await fetch(`/api/config/3?group=${group}`); const response = await fetch(`/api/config/3?group=${group}`);
@ -73,14 +73,14 @@ async function loadConfigs() {
const data = await response.json(); const data = await response.json();
console.log('Level 3 data:', data); console.log('Level 3 data:', data);
await renderForm('level3-form', data); await renderForm('level3-form', data);
// Actualizar input del directorio de trabajo // Actualizar input del directorio de trabajo
document.getElementById('working-directory').value = workingDirResult.path; document.getElementById('working-directory').value = workingDirResult.path;
} }
// Cargar scripts disponibles // Cargar scripts disponibles
await loadScripts(group); await loadScripts(group);
} catch (error) { } catch (error) {
console.error('Error loading configs:', error); console.error('Error loading configs:', error);
} }
@ -193,15 +193,15 @@ async function loadScripts(group) {
</div> </div>
<div id="long-desc-${script.filename}" class="long-description-content mt-2 border-t pt-2 hidden"> <div id="long-desc-${script.filename}" class="long-description-content mt-2 border-t pt-2 hidden">
${script.long_description ? (() => { // Self-invoking function to handle markdown rendering ${script.long_description ? (() => { // Self-invoking function to handle markdown rendering
if (typeof window.markdownit === 'undefined') { // Check if markdownit is loaded if (typeof window.markdownit === 'undefined') { // Check if markdownit is loaded
console.error("markdown-it library not loaded!"); console.error("markdown-it library not loaded!");
return `<p class="text-red-500">Error: Librería Markdown no cargada.</p><pre>${script.long_description}</pre>`; // Fallback: show raw text return `<p class="text-red-500">Error: Librería Markdown no cargada.</p><pre>${script.long_description}</pre>`; // Fallback: show raw text
} }
// Create instance and render // Create instance and render
const md = window.markdownit(); const md = window.markdownit();
const renderedHtml = md.render(script.long_description); // Renderizar const renderedHtml = md.render(script.long_description); // Renderizar
return renderedHtml; return renderedHtml;
})() : ''} })() : ''}
</div> </div>
</div> </div>
<div class="flex items-center gap-2 flex-shrink-0"> <div class="flex items-center gap-2 flex-shrink-0">
@ -264,11 +264,11 @@ async function executeScript(scriptName) {
// Check for HTTP errors during the *request* itself // Check for HTTP errors during the *request* itself
if (!response.ok) { if (!response.ok) {
const errorText = await response.text(); const errorText = await response.text();
console.error(`Error initiating script execution request: ${response.status} ${response.statusText}`, errorText); console.error(`Error initiating script execution request: ${response.status} ${response.statusText}`, errorText);
// Log only the request error, not script execution errors which come via WebSocket // Log only the request error, not script execution errors which come via WebSocket
addLogLine(`\nError al iniciar la petición del script: ${response.status} ${errorText}\n`); addLogLine(`\nError al iniciar la petición del script: ${response.status} ${errorText}\n`);
return; // Stop if the request failed return; // Stop if the request failed
} }
// REMOVE logging the result/error here - let the backend log via WebSocket // REMOVE logging the result/error here - let the backend log via WebSocket
@ -291,7 +291,7 @@ async function renderForm(containerId, data) {
console.log(`Rendering form for ${containerId} with data:`, data); // Debug line console.log(`Rendering form for ${containerId} with data:`, data); // Debug line
const container = document.getElementById(containerId); const container = document.getElementById(containerId);
const level = containerId.replace('level', '').split('-')[0]; const level = containerId.replace('level', '').split('-')[0];
try { try {
const schemaResponse = await fetch(`/api/schema/${level}?group=${currentGroup}`); const schemaResponse = await fetch(`/api/schema/${level}?group=${currentGroup}`);
const schema = await schemaResponse.json(); const schema = await schemaResponse.json();
@ -339,7 +339,7 @@ async function renderForm(containerId, data) {
function generateFormFields(schema, data, prefix, level) { function generateFormFields(schema, data, prefix, level) {
console.log('Generating fields with data:', { schema, data, prefix, level }); // Debug line console.log('Generating fields with data:', { schema, data, prefix, level }); // Debug line
let html = ''; let html = '';
if (!schema.properties) { if (!schema.properties) {
console.warn('Schema has no properties'); console.warn('Schema has no properties');
return html; return html;
@ -349,10 +349,10 @@ function generateFormFields(schema, data, prefix, level) {
const fullKey = prefix ? `${prefix}.${key}` : key; const fullKey = prefix ? `${prefix}.${key}` : key;
const value = getValue(data, fullKey); const value = getValue(data, fullKey);
console.log(`Field ${fullKey}:`, { definition: def, value: value }); // Debug line console.log(`Field ${fullKey}:`, { definition: def, value: value }); // Debug line
html += `<div class="mb-4"> html += `<div class="mb-4">
<label class="block text-gray-700 text-sm font-bold mb-2">${def.title || key}</label>`; <label class="block text-gray-700 text-sm font-bold mb-2">${def.title || key}</label>`;
if (def.type === 'object') { if (def.type === 'object') {
html += `<div class="pl-4 border-l-2 border-gray-200"> html += `<div class="pl-4 border-l-2 border-gray-200">
${generateFormFields(def, data, fullKey, level)} ${generateFormFields(def, data, fullKey, level)}
@ -360,7 +360,7 @@ function generateFormFields(schema, data, prefix, level) {
} else { } else {
html += generateInputField(def, fullKey, value, level); html += generateInputField(def, fullKey, value, level);
} }
if (def.description) { if (def.description) {
html += `<p class="text-gray-500 text-xs mt-1">${def.description}</p>`; html += `<p class="text-gray-500 text-xs mt-1">${def.description}</p>`;
} }
@ -372,7 +372,7 @@ function generateFormFields(schema, data, prefix, level) {
function getValue(data, path) { function getValue(data, path) {
console.log('Getting value for path:', { path, data }); // Debug line console.log('Getting value for path:', { path, data }); // Debug line
if (!data || !path) return undefined; if (!data || !path) return undefined;
const value = path.split('.').reduce((obj, key) => obj?.[key], data); const value = path.split('.').reduce((obj, key) => obj?.[key], data);
console.log('Found value:', value); // Debug line console.log('Found value:', value); // Debug line
return value; return value;
@ -381,7 +381,7 @@ function getValue(data, path) {
// Modificar la función generateInputField para quitar el onchange // Modificar la función generateInputField para quitar el onchange
function generateInputField(def, key, value, level) { function generateInputField(def, key, value, level) {
const baseClasses = "w-full p-2 border rounded bg-green-50"; const baseClasses = "w-full p-2 border rounded bg-green-50";
switch (def.type) { switch (def.type) {
case 'string': case 'string':
if (def.format === 'directory') { if (def.format === 'directory') {
@ -403,11 +403,11 @@ function generateInputField(def, key, value, level) {
} }
return `<input type="text" value="${value || ''}" return `<input type="text" value="${value || ''}"
class="${baseClasses}" data-key="${key}">`; class="${baseClasses}" data-key="${key}">`;
case 'number': case 'number':
return `<input type="number" value="${value || 0}" return `<input type="number" value="${value || 0}"
class="${baseClasses}" data-key="${key}">`; class="${baseClasses}" data-key="${key}">`;
case 'boolean': case 'boolean':
return `<input type="checkbox" ${value ? 'checked' : ''} return `<input type="checkbox" ${value ? 'checked' : ''}
class="form-checkbox h-5 w-5 bg-green-50" data-key="${key}"> class="form-checkbox h-5 w-5 bg-green-50" data-key="${key}">
@ -426,7 +426,7 @@ async function browseFieldDirectory(button) {
try { try {
const response = await fetch(`/api/browse-directories?current_path=${encodeURIComponent(currentPath)}`); const response = await fetch(`/api/browse-directories?current_path=${encodeURIComponent(currentPath)}`);
const result = await response.json(); const result = await response.json();
if (result.status === 'success') { if (result.status === 'success') {
input.value = result.path; input.value = result.path;
// Disparar un evento change para actualizar el valor internamente // Disparar un evento change para actualizar el valor internamente
@ -448,23 +448,23 @@ async function modifySchema(level) {
} }
const schema = await response.json(); const schema = await response.json();
console.log('Loaded schema:', schema); // Debug line console.log('Loaded schema:', schema); // Debug line
// Show schema editor modal // Show schema editor modal
const modal = document.getElementById('schema-editor'); const modal = document.getElementById('schema-editor');
if (!modal) { if (!modal) {
throw new Error('Schema editor modal not found'); throw new Error('Schema editor modal not found');
} }
modal.classList.remove('hidden'); modal.classList.remove('hidden');
// Inicializar el esquema si está vacío // Inicializar el esquema si está vacío
const finalSchema = Object.keys(schema).length === 0 ? const finalSchema = Object.keys(schema).length === 0 ?
{ type: 'object', properties: {} } : schema; { type: 'object', properties: {} } : schema;
// Inicializar editores // Inicializar editores
const jsonEditor = document.getElementById('json-editor'); const jsonEditor = document.getElementById('json-editor');
const visualEditor = document.getElementById('visual-editor'); const visualEditor = document.getElementById('visual-editor');
const schemaLevel = document.getElementById('schema-level'); const schemaLevel = document.getElementById('schema-level');
if (!jsonEditor || !visualEditor || !schemaLevel) { if (!jsonEditor || !visualEditor || !schemaLevel) {
throw new Error('Required editor elements not found'); throw new Error('Required editor elements not found');
} }
@ -473,10 +473,10 @@ async function modifySchema(level) {
visualEditor.innerHTML = '<div id="schema-fields" class="mb-4"></div>' + visualEditor.innerHTML = '<div id="schema-fields" class="mb-4"></div>' +
'<button onclick="addSchemaField()" class="mt-4 bg-green-500 text-white px-4 py-2 rounded">Agregar Campo</button>'; '<button onclick="addSchemaField()" class="mt-4 bg-green-500 text-white px-4 py-2 rounded">Agregar Campo</button>';
schemaLevel.value = level; schemaLevel.value = level;
// Renderizar editor visual // Renderizar editor visual
renderVisualEditor(finalSchema); renderVisualEditor(finalSchema);
// Activar pestaña visual por defecto // Activar pestaña visual por defecto
switchEditorMode('visual'); switchEditorMode('visual');
} catch (error) { } catch (error) {
@ -588,7 +588,7 @@ function createFieldEditor(key, field) {
function updateFieldType(select) { function updateFieldType(select) {
const fieldContainer = select.closest('.schema-field'); const fieldContainer = select.closest('.schema-field');
const enumContainer = fieldContainer.querySelector('.enum-container'); const enumContainer = fieldContainer.querySelector('.enum-container');
if (select.value === 'enum') { if (select.value === 'enum') {
if (!enumContainer) { if (!enumContainer) {
const div = document.createElement('div'); const div = document.createElement('div');
@ -677,11 +677,11 @@ function updateVisualSchema() {
console.warn(`Valor por defecto inválido para número en campo '${key}': ${defaultValueString}. Se omitirá.`); console.warn(`Valor por defecto inválido para número en campo '${key}': ${defaultValueString}. Se omitirá.`);
// No añadir default si no es un número válido // No añadir default si no es un número válido
} else { } else {
// Opcional: truncar si el tipo es integer // Opcional: truncar si el tipo es integer
if (propertyDefinition.type === 'integer' && !Number.isInteger(typedDefaultValue)) { if (propertyDefinition.type === 'integer' && !Number.isInteger(typedDefaultValue)) {
typedDefaultValue = Math.trunc(typedDefaultValue); typedDefaultValue = Math.trunc(typedDefaultValue);
} }
propertyDefinition.default = typedDefaultValue; propertyDefinition.default = typedDefaultValue;
} }
} else if (propertyDefinition.type === 'boolean') { } else if (propertyDefinition.type === 'boolean') {
typedDefaultValue = ['true', '1', 'yes', 'on'].includes(defaultValueString.toLowerCase()); typedDefaultValue = ['true', '1', 'yes', 'on'].includes(defaultValueString.toLowerCase());
@ -730,16 +730,16 @@ async function saveSchema() {
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(schema) body: JSON.stringify(schema)
}); });
if (!response.ok) { if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`); throw new Error(`HTTP error! status: ${response.status}`);
} }
// Recargar el formulario // Recargar el formulario
const configResponse = await fetch(`/api/config/${level}?group=${currentGroup}`); const configResponse = await fetch(`/api/config/${level}?group=${currentGroup}`);
const data = await configResponse.json(); const data = await configResponse.json();
await renderForm(`level${level}-form`, data); await renderForm(`level${level}-form`, data);
// Cerrar modal // Cerrar modal
document.getElementById('schema-editor').classList.add('hidden'); document.getElementById('schema-editor').classList.add('hidden');
} catch (e) { } catch (e) {
@ -761,7 +761,7 @@ async function setWorkingDirectory() {
// Modificar initWorkingDirectory para cargar también el historial // Modificar initWorkingDirectory para cargar también el historial
async function initWorkingDirectory() { async function initWorkingDirectory() {
if (!currentGroup) return; if (!currentGroup) return;
const response = await fetch(`/api/working-directory/${currentGroup}`); const response = await fetch(`/api/working-directory/${currentGroup}`);
const result = await response.json(); const result = await response.json();
if (result.status === 'success' && result.path) { if (result.status === 'success' && result.path) {
@ -780,7 +780,7 @@ async function browseDirectory() {
const currentPath = document.getElementById('working-directory').value; const currentPath = document.getElementById('working-directory').value;
const response = await fetch(`/api/browse-directories?current_path=${encodeURIComponent(currentPath)}`); const response = await fetch(`/api/browse-directories?current_path=${encodeURIComponent(currentPath)}`);
const result = await response.json(); const result = await response.json();
if (result.status === 'success') { if (result.status === 'success') {
await updateWorkingDirectory(result.path); await updateWorkingDirectory(result.path);
} }
@ -794,12 +794,12 @@ async function updateWorkingDirectory(path) {
const response = await fetch('/api/working-directory', { const response = await fetch('/api/working-directory', {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ body: JSON.stringify({
path: path, path: path,
group: currentGroup group: currentGroup
}) })
}); });
const result = await response.json(); const result = await response.json();
console.log('Update result:', result); // Debug line console.log('Update result:', result); // Debug line
@ -807,7 +807,7 @@ async function updateWorkingDirectory(path) {
// Actualizar input y lista de directorios // Actualizar input y lista de directorios
document.getElementById('working-directory').value = path; document.getElementById('working-directory').value = path;
await loadDirectoryHistory(); await loadDirectoryHistory();
// Recargar configuración de nivel 3 // Recargar configuración de nivel 3
const configResponse = await fetch(`/api/config/3?group=${currentGroup}`); const configResponse = await fetch(`/api/config/3?group=${currentGroup}`);
const data = await configResponse.json(); const data = await configResponse.json();
@ -825,10 +825,10 @@ async function loadDirectoryHistory() {
try { try {
const response = await fetch(`/api/directory-history/${currentGroup}`); const response = await fetch(`/api/directory-history/${currentGroup}`);
const history = await response.json(); const history = await response.json();
const select = document.getElementById('directory-history'); const select = document.getElementById('directory-history');
select.innerHTML = '<option value="">-- Directorios recientes --</option>'; select.innerHTML = '<option value="">-- Directorios recientes --</option>';
history.forEach(dir => { history.forEach(dir => {
const option = document.createElement('option'); const option = document.createElement('option');
option.value = dir; option.value = dir;
@ -855,16 +855,16 @@ function loadHistoryDirectory(path) {
function toggleConfig(sectionId) { function toggleConfig(sectionId) {
const content = document.getElementById(sectionId); const content = document.getElementById(sectionId);
const button = document.querySelector(`[onclick="toggleConfig('${sectionId}')"]`); const button = document.querySelector(`[onclick="toggleConfig('${sectionId}')"]`);
if (content.classList.contains('hidden')) { if (content.classList.contains('hidden')) {
content.classList.remove('hidden'); content.classList.remove('hidden');
button.innerText = 'Ocultar Configuración'; button.innerText = 'Ocultar Configuración';
// Recargar la configuración al mostrar // Recargar la configuración al mostrar
const level = sectionId.replace('level', '').replace('-content', ''); const level = sectionId.replace('level', '').replace('-content', '');
const formId = `level${level}-form`; const formId = `level${level}-form`;
console.log(`Reloading config for level ${level}`); // Debug line console.log(`Reloading config for level ${level}`); // Debug line
fetch(`/api/config/${level}?group=${currentGroup}`) fetch(`/api/config/${level}?group=${currentGroup}`)
.then(response => response.json()) .then(response => response.json())
.then(data => renderForm(formId, data)) .then(data => renderForm(formId, data))
@ -876,10 +876,14 @@ function toggleConfig(sectionId) {
} }
async function clearLogs() { async function clearLogs() {
// Eliminada la confirmación - directamente procede a limpiar
const response = await fetch('/api/logs', { method: 'DELETE' }); const response = await fetch('/api/logs', { method: 'DELETE' });
const result = await response.json(); const result = await response.json();
if (result.status === 'success') { if (result.status === 'success') {
document.getElementById('log-area').innerHTML = ''; document.getElementById('log-area').innerHTML = '';
showToast ? showToast('Logs borrados correctamente', 'success') : console.log('Logs cleared');
} else {
showToast ? showToast('Error al borrar los logs', 'error') : alert('Error al borrar los logs');
} }
} }
@ -897,11 +901,11 @@ async function initializeApp() {
// Inicializar WebSocket // Inicializar WebSocket
initWebSocket(); initWebSocket();
await loadStoredLogs(); await loadStoredLogs();
// Configurar grupo actual // Configurar grupo actual
const selectElement = document.getElementById('script-group'); const selectElement = document.getElementById('script-group');
currentGroup = localStorage.getItem('selectedGroup') || selectElement.value; currentGroup = localStorage.getItem('selectedGroup') || selectElement.value;
// Actualizar el select con el valor guardado // Actualizar el select con el valor guardado
if (currentGroup) { if (currentGroup) {
selectElement.value = currentGroup; selectElement.value = currentGroup;
@ -909,10 +913,10 @@ async function initializeApp() {
// Limpiar evento anterior si existe // Limpiar evento anterior si existe
selectElement.removeEventListener('change', handleGroupChange); selectElement.removeEventListener('change', handleGroupChange);
// Agregar el nuevo manejador de eventos // Agregar el nuevo manejador de eventos
selectElement.addEventListener('change', handleGroupChange); selectElement.addEventListener('change', handleGroupChange);
// Event listener para el nuevo botón de abrir en explorador // Event listener para el nuevo botón de abrir en explorador
const openInExplorerButton = document.getElementById('open-in-explorer-btn'); const openInExplorerButton = document.getElementById('open-in-explorer-btn');
if (openInExplorerButton) { if (openInExplorerButton) {
@ -945,7 +949,7 @@ async function handleGroupChange(e) {
currentGroup = e.target.value; currentGroup = e.target.value;
localStorage.setItem('selectedGroup', currentGroup); localStorage.setItem('selectedGroup', currentGroup);
console.log('Group changed to:', currentGroup); console.log('Group changed to:', currentGroup);
// Limpiar formularios existentes // Limpiar formularios existentes
['level1-form', 'level2-form', 'level3-form'].forEach(id => { ['level1-form', 'level2-form', 'level3-form'].forEach(id => {
const element = document.getElementById(id); const element = document.getElementById(id);
@ -974,8 +978,8 @@ document.addEventListener('DOMContentLoaded', () => {
// Función auxiliar para obtener timestamp formateado // Función auxiliar para obtener timestamp formateado
function getTimestamp() { function getTimestamp() {
const now = new Date(); const now = new Date();
return now.toLocaleTimeString('es-ES', { return now.toLocaleTimeString('es-ES', {
hour: '2-digit', hour: '2-digit',
minute: '2-digit', minute: '2-digit',
second: '2-digit' second: '2-digit'
}); });
@ -1007,12 +1011,12 @@ function toggleSidebar() {
const sidebar = document.querySelector('.sidebar'); const sidebar = document.querySelector('.sidebar');
const overlay = document.querySelector('.overlay'); const overlay = document.querySelector('.overlay');
const schemaEditor = document.getElementById('schema-editor'); const schemaEditor = document.getElementById('schema-editor');
// No cerrar sidebar si el modal está abierto // No cerrar sidebar si el modal está abierto
if (!schemaEditor.classList.contains('hidden')) { if (!schemaEditor.classList.contains('hidden')) {
return; return;
} }
sidebar.classList.toggle('open'); sidebar.classList.toggle('open');
overlay.classList.toggle('show'); overlay.classList.toggle('show');
} }
@ -1038,7 +1042,7 @@ async function editGroupDescription() {
// Configurar modal para edición de descripción // Configurar modal para edición de descripción
modalTitle.textContent = 'Editar Descripción del Grupo'; modalTitle.textContent = 'Editar Descripción del Grupo';
tabs.classList.add('hidden'); tabs.classList.add('hidden');
// Crear el formulario en el visualEditor // Crear el formulario en el visualEditor
visualEditor.innerHTML = ` visualEditor.innerHTML = `
<form id="group-description-form" class="grid gap-4"> <form id="group-description-form" class="grid gap-4">
@ -1068,9 +1072,9 @@ async function editGroupDescription() {
`; `;
visualEditor.classList.remove('hidden'); visualEditor.classList.remove('hidden');
jsonEditor.classList.add('hidden'); jsonEditor.classList.add('hidden');
modal.classList.remove('hidden'); modal.classList.remove('hidden');
// Cambiar comportamiento de todos los botones de guardar // Cambiar comportamiento de todos los botones de guardar
const saveButtons = modal.querySelectorAll('button[onclick="saveSchema()"]'); const saveButtons = modal.querySelectorAll('button[onclick="saveSchema()"]');
saveButtons.forEach(btn => { saveButtons.forEach(btn => {
@ -1078,7 +1082,7 @@ async function editGroupDescription() {
try { try {
const form = document.getElementById('group-description-form'); const form = document.getElementById('group-description-form');
const formData = new FormData(form); const formData = new FormData(form);
const updatedDescription = { const updatedDescription = {
name: formData.get('name') || '', name: formData.get('name') || '',
description: formData.get('description') || '', description: formData.get('description') || '',
@ -1091,15 +1095,15 @@ async function editGroupDescription() {
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(updatedDescription) body: JSON.stringify(updatedDescription)
}); });
if (!saveResponse.ok) throw new Error('Error guardando descripción'); if (!saveResponse.ok) throw new Error('Error guardando descripción');
// Restaurar modal a su estado original // Restaurar modal a su estado original
modalTitle.textContent = 'Editor de Esquema'; modalTitle.textContent = 'Editor de Esquema';
tabs.classList.remove('hidden'); tabs.classList.remove('hidden');
saveButtons.forEach(btn => btn.onclick = saveSchema); saveButtons.forEach(btn => btn.onclick = saveSchema);
modal.classList.add('hidden'); modal.classList.add('hidden');
// Recargar la página para actualizar la descripción // Recargar la página para actualizar la descripción
location.reload(); location.reload();
} catch (e) { } catch (e) {
@ -1117,11 +1121,11 @@ async function editGroupDescription() {
function collectFormData(level) { function collectFormData(level) {
const formContainer = document.getElementById(`level${level}-form`); const formContainer = document.getElementById(`level${level}-form`);
const data = {}; const data = {};
formContainer.querySelectorAll('input, select').forEach(input => { formContainer.querySelectorAll('input, select').forEach(input => {
const key = input.getAttribute('data-key'); const key = input.getAttribute('data-key');
if (!key) return; if (!key) return;
let value; let value;
if (input.type === 'checkbox') { if (input.type === 'checkbox') {
value = input.checked; value = input.checked;
@ -1130,7 +1134,7 @@ function collectFormData(level) {
} else { } else {
value = input.value; value = input.value;
} }
// Manejar claves anidadas (por ejemplo: "parent.child") // Manejar claves anidadas (por ejemplo: "parent.child")
const keys = key.split('.'); const keys = key.split('.');
let current = data; let current = data;
@ -1140,7 +1144,7 @@ function collectFormData(level) {
} }
current[keys[keys.length - 1]] = value; current[keys[keys.length - 1]] = value;
}); });
return data; return data;
} }
@ -1154,23 +1158,23 @@ function shutdownServer() {
'Content-Type': 'application/json' 'Content-Type': 'application/json'
} }
}) })
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
if (data.status === 'success') { if (data.status === 'success') {
alert("El servidor se está deteniendo. Puede que necesites cerrar esta pestaña manualmente."); alert("El servidor se está deteniendo. Puede que necesites cerrar esta pestaña manualmente.");
// Opcionalmente, puedes intentar cerrar la ventana/pestaña // Opcionalmente, puedes intentar cerrar la ventana/pestaña
// window.close(); // Esto puede no funcionar en todos los navegadores por seguridad // window.close(); // Esto puede no funcionar en todos los navegadores por seguridad
document.body.innerHTML = '<div class="alert alert-info">El servidor se ha detenido. Cierra esta ventana.</div>'; document.body.innerHTML = '<div class="alert alert-info">El servidor se ha detenido. Cierra esta ventana.</div>';
} else { } else {
alert("Error al intentar detener el servidor: " + data.message); alert("Error al intentar detener el servidor: " + data.message);
} }
}) })
.catch(error => { .catch(error => {
// Es normal recibir un error de red aquí porque el servidor se está apagando // Es normal recibir un error de red aquí porque el servidor se está apagando
console.warn("Error esperado al detener el servidor (puede que ya se haya detenido):", error); console.warn("Error esperado al detener el servidor (puede que ya se haya detenido):", error);
alert("Solicitud de detención enviada. El servidor debería detenerse. Cierra esta ventana."); alert("Solicitud de detención enviada. El servidor debería detenerse. Cierra esta ventana.");
document.body.innerHTML = '<div class="alert alert-info">El servidor se está deteniendo. Cierra esta ventana.</div>'; document.body.innerHTML = '<div class="alert alert-info">El servidor se está deteniendo. Cierra esta ventana.</div>';
}); });
} }
} }
@ -1188,26 +1192,6 @@ function fetchLogs() {
.catch(error => console.error('Error fetching logs:', error)); .catch(error => console.error('Error fetching logs:', error));
} }
function clearLogs() {
if (confirm("¿Estás seguro de que quieres borrar los logs?")) {
fetch('/api/logs', { method: 'DELETE' })
.then(response => response.json())
.then(data => {
if (data.status === 'success') {
// Limpiar el área de log visualmente AHORA
document.getElementById('log-area').innerHTML = '';
showToast('Logs borrados correctamente.');
} else {
showToast('Error al borrar los logs.', 'error');
}
})
.catch(error => {
console.error('Error clearing logs:', error);
showToast('Error de red al borrar los logs.', 'error');
});
}
}
// Necesitarás una función showToast o similar si la usas // Necesitarás una función showToast o similar si la usas
function showToast(message, type = 'success') { function showToast(message, type = 'success') {
// Implementa tu lógica de Toast aquí // Implementa tu lógica de Toast aquí
@ -1228,7 +1212,7 @@ async function saveConfig(level) {
const originalText = saveButton.innerText; const originalText = saveButton.innerText;
const originalClasses = 'bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded transition-colors duration-300'; const originalClasses = 'bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded transition-colors duration-300';
try { try {
saveButton.disabled = true; saveButton.disabled = true;
saveButton.className = 'bg-yellow-500 text-white px-4 py-2 rounded cursor-wait transition-colors duration-300'; saveButton.className = 'bg-yellow-500 text-white px-4 py-2 rounded cursor-wait transition-colors duration-300';
@ -1250,7 +1234,7 @@ async function saveConfig(level) {
if (result.status === 'success') { if (result.status === 'success') {
saveButton.className = 'bg-green-500 text-white px-4 py-2 rounded transition-colors duration-300'; saveButton.className = 'bg-green-500 text-white px-4 py-2 rounded transition-colors duration-300';
saveButton.innerText = '¡Guardado con Éxito!'; saveButton.innerText = '¡Guardado con Éxito!';
setTimeout(() => { setTimeout(() => {
if (saveButton) { if (saveButton) {
saveButton.className = originalClasses; saveButton.className = originalClasses;
@ -1265,7 +1249,7 @@ async function saveConfig(level) {
console.error('Error saving config:', error); console.error('Error saving config:', error);
saveButton.className = 'bg-red-500 text-white px-4 py-2 rounded transition-colors duration-300'; saveButton.className = 'bg-red-500 text-white px-4 py-2 rounded transition-colors duration-300';
saveButton.innerText = 'Error al Guardar'; saveButton.innerText = 'Error al Guardar';
setTimeout(() => { setTimeout(() => {
if (saveButton) { if (saveButton) {
saveButton.className = originalClasses; saveButton.className = originalClasses;
@ -1289,7 +1273,7 @@ async function openGroupInEditor(editorCode, groupSystem, groupId) {
const response = await fetch(`/api/open-editor/${editorCode}/${groupSystem}/${groupId}`, { const response = await fetch(`/api/open-editor/${editorCode}/${groupSystem}/${groupId}`, {
method: 'POST' method: 'POST'
}); });
if (!response.ok) { if (!response.ok) {
// If response is not OK, it might not be JSON (e.g., Flask error page) // If response is not OK, it might not be JSON (e.g., Flask error page)
const errorText = await response.text(); const errorText = await response.text();
@ -1317,18 +1301,18 @@ function openMinicondaConsole() {
'Content-Type': 'application/json' 'Content-Type': 'application/json'
} }
}) })
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
if (data.status === 'success') { if (data.status === 'success') {
showNotification('Miniconda Console abierta correctamente', 'success'); showNotification('Miniconda Console abierta correctamente', 'success');
} else { } else {
showNotification(`Error al abrir Miniconda Console: ${data.message}`, 'error'); showNotification(`Error al abrir Miniconda Console: ${data.message}`, 'error');
} }
}) })
.catch(error => { .catch(error => {
console.error('Error opening Miniconda Console:', error); console.error('Error opening Miniconda Console:', error);
showNotification('Error al comunicarse con el servidor', 'error'); showNotification('Error al comunicarse con el servidor', 'error');
}); });
} }
async function openCurrentWorkingDirectoryInExplorer() { async function openCurrentWorkingDirectoryInExplorer() {
@ -1365,4 +1349,69 @@ async function openCurrentWorkingDirectoryInExplorer() {
console.error("Error de red al abrir en explorador:", error); console.error("Error de red al abrir en explorador:", error);
showToast("Error de red al intentar abrir el explorador.", "error"); showToast("Error de red al intentar abrir el explorador.", "error");
} }
}
async function openGroupFolder(groupSystem, groupId) {
if (!groupId) {
alert('Por favor, seleccione un grupo de scripts primero');
return;
}
try {
const response = await fetch(`/api/open-group-folder/${groupSystem}/${groupId}`, {
method: 'POST'
});
const result = await response.json();
if (result.status === 'success') {
console.log(`Carpeta abierta: ${result.path}`);
} else {
alert(`Error al abrir carpeta: ${result.message}`);
}
} catch (error) {
console.error('Error opening folder:', error);
alert('Error al comunicarse con el servidor');
}
}
async function copyGroupPath(groupSystem, groupId) {
if (!groupId) {
alert('Por favor, seleccione un grupo de scripts primero');
return;
}
try {
const response = await fetch(`/api/get-group-path/${groupSystem}/${groupId}`);
const result = await response.json();
if (result.status === 'success') {
// Copiar al portapapeles usando la API moderna
if (navigator.clipboard && navigator.clipboard.writeText) {
await navigator.clipboard.writeText(result.path);
showToast ? showToast('Path copiado al portapapeles', 'success') : alert('Path copiado al portapapeles');
} else {
// Fallback para navegadores más antiguos
const textArea = document.createElement('textarea');
textArea.value = result.path;
document.body.appendChild(textArea);
textArea.focus();
textArea.select();
try {
document.execCommand('copy');
showToast ? showToast('Path copiado al portapapeles', 'success') : alert('Path copiado al portapapeles');
} catch (err) {
console.error('Error copying to clipboard:', err);
alert(`Error al copiar. Path: ${result.path}`);
}
document.body.removeChild(textArea);
}
} else {
alert(`Error al obtener path: ${result.message}`);
}
} catch (error) {
console.error('Error getting path:', error);
alert('Error al comunicarse con el servidor');
}
} }

View File

@ -153,6 +153,18 @@
<img src="{{ url_for('static', filename='icons/cursor.png') }}" class="w-5 h-5" <img src="{{ url_for('static', filename='icons/cursor.png') }}" class="w-5 h-5"
alt="Cursor Icon"> alt="Cursor Icon">
</button> </button>
<!-- Botón para abrir carpeta del grupo -->
<button onclick="openGroupFolder('config', currentGroup)"
class="bg-green-500 text-white p-2 rounded mb-2" id="folder-config-btn"
title="Abrir carpeta del grupo">
📁
</button>
<!-- Botón para copiar path del grupo -->
<button onclick="copyGroupPath('config', currentGroup)"
class="bg-gray-500 text-white p-2 rounded mb-2" id="copy-path-config-btn"
title="Copiar path del grupo">
📋
</button>
</div> </div>
<p id="group-description" class="text-gray-600 text-sm italic"></p> <p id="group-description" class="text-gray-600 text-sm italic"></p>
@ -257,7 +269,18 @@
<img src="{{ url_for('static', filename='icons/cursor.png') }}" class="w-5 h-5" <img src="{{ url_for('static', filename='icons/cursor.png') }}" class="w-5 h-5"
alt="Cursor Icon"> alt="Cursor Icon">
</button> </button>
<!-- Botón para abrir carpeta del grupo launcher -->
<button onclick="launcherManager.openGroupFolder()"
class="bg-green-500 text-white px-4 py-3 rounded-lg hover:bg-green-600"
id="folder-launcher-btn" style="display: none;" title="Abrir carpeta del grupo">
📁
</button>
<!-- Botón para copiar path del grupo launcher -->
<button onclick="launcherManager.copyGroupPath()"
class="bg-gray-500 text-white px-4 py-3 rounded-lg hover:bg-gray-600"
id="copy-path-launcher-btn" style="display: none;" title="Copiar path del grupo">
📋
</button>
</div> </div>
</div> </div>
@ -328,13 +351,17 @@
<!-- History Panel --> <!-- History Panel -->
<div class="mb-6 bg-white p-6 rounded-lg shadow"> <div class="mb-6 bg-white p-6 rounded-lg shadow">
<div class="flex justify-between items-center mb-4"> <div class="flex justify-between items-center mb-4 cursor-pointer" onclick="toggleHistoryPanel()">
<h3 class="text-lg font-semibold">📝 Historial Reciente</h3> <div class="flex items-center">
<button onclick="clearLauncherHistory()" class="text-red-500 hover:text-red-700 text-sm"> <span id="history-toggle-icon" class="mr-2"></span>
<h3 class="text-lg font-semibold">📝 Historial Reciente</h3>
</div>
<button onclick="event.stopPropagation(); clearLauncherHistory()"
class="text-red-500 hover:text-red-700 text-sm">
Limpiar Historial Limpiar Historial
</button> </button>
</div> </div>
<div id="history-list" class="space-y-2 max-h-64 overflow-y-auto"> <div id="history-list" class="space-y-2 max-h-64 overflow-y-auto hidden">
<!-- Lista dinámica de historial --> <!-- Lista dinámica de historial -->
</div> </div>
</div> </div>