Skip to content

Commit 90bfc77

Browse files
committed
Cambios minimos
1 parent ab299eb commit 90bfc77

3 files changed

Lines changed: 119 additions & 49 deletions

File tree

main.py

Lines changed: 54 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
"""
99

1010
import eel
11+
import eel.browsers
1112
import os
1213
import sys
1314
import platform
@@ -36,28 +37,51 @@
3637
cipher = Fernet(SECRET_KEY)
3738

3839
BASE_PATH = file_handler.BASE_PATH
39-
# Definir ruta al navegador portable (dentro del proyecto)
40-
if platform.system() == "Windows":
41-
BROWSER_PATH = os.path.join(BASE_PATH, "chromium", "chrome.exe")
42-
else:
43-
# Asumimos Linux/Mac
44-
BROWSER_PATH = os.path.join(BASE_PATH, "chromium", "chrome")
40+
def get_chromium_path():
41+
"""Detecta la ruta de Chromium considerando PyInstaller (_internal) y el Sistema Operativo"""
42+
# Detectar si estamos en el .exe o en VS Code
43+
if hasattr(sys, '_MEIPASS'):
44+
base_dir = sys._MEIPASS
45+
else:
46+
base_dir = os.path.abspath(os.path.dirname(__file__))
47+
48+
# Detectar el Sistema Operativo para el nombre del ejecutable
49+
if platform.system() == "Windows":
50+
exe_name = "chrome.exe"
51+
else:
52+
# En Linux/Mac los binarios no llevan .exe
53+
exe_name = "chrome" # O "chromium"
54+
55+
return os.path.join(base_dir, 'chromium', exe_name)
4556

46-
# Configurar Eel para usar este navegador Específico
47-
if os.path.exists(BROWSER_PATH):
48-
print(f"[INFO] Usando Chromium Portable: {BROWSER_PATH}")
49-
eel.browsers.set_path('chrome', BROWSER_PATH)
50-
else:
51-
print("[WARN] Chromium portable no encontrado. Intentando navegador del sistema...")
57+
CHROMIUM_PATH = get_chromium_path()
5258

53-
# Inicialización de Eel
54-
# Aseguramos que la carpeta web exista
55-
if not os.path.exists('web'):
56-
print("[ERROR CRÍTICO] La carpeta 'web' no existe. Asegúrate de haber creado el index.html allí.")
59+
# Configurar Eel para usar este navegador específico si existe
60+
if os.path.exists(CHROMIUM_PATH):
61+
print(f"[INFO] Usando Chromium Portable en: {CHROMIUM_PATH}")
62+
eel.browsers.set_path('chrome', CHROMIUM_PATH)
63+
else:
64+
print(f"[WARN] Chromium portable no encontrado en: {CHROMIUM_PATH}")
65+
print("El sistema usará el navegador por defecto (Edge/Chrome/Safari) como respaldo.")
66+
67+
def get_web_path():
68+
"""Detecta inteligentemente dónde está la carpeta web (VS Code vs .exe)"""
69+
if hasattr(sys, '_MEIPASS'):
70+
# Si estamos dentro del .exe, PyInstaller guarda los datos en _MEIPASS (carpeta _internal)
71+
return os.path.join(sys._MEIPASS, 'web')
72+
else:
73+
# Si estamos ejecutando el script normal en VS Code
74+
return os.path.join(os.path.abspath(os.path.dirname(__file__)), 'web')
75+
76+
WEB_PATH = get_web_path()
77+
78+
if not os.path.exists(WEB_PATH):
79+
print(f"[ERROR CRÍTICO] La carpeta 'web' no existe en la ruta esperada: {WEB_PATH}")
80+
input("Presiona Enter para salir...")
5781
sys.exit()
5882

59-
# Inicializamos apuntando a la carpeta donde está index.html
60-
eel.init('web')
83+
# Inicializamos Eel apuntando a la ruta absoluta blindada
84+
eel.init(WEB_PATH)
6185

6286
# ---------------------------------------------------------
6387
# FUNCIONES EXPUESTAS A JAVASCRIPT (API DE EEL)
@@ -109,8 +133,8 @@ def api_load_project(filename):
109133
except Exception as e:
110134
return {'error': f"Error de lectura: {str(e)}"}
111135

112-
@eel.expose
113-
def api_save_model_file(data, filename):
136+
#@eel.expose
137+
#*def api_save_model_file(data, filename):
114138
"""
115139
Guarda la arquitectura del modelo ML (nodos/conexiones)
116140
como archivo .edubotml en la carpeta 'models'.
@@ -271,8 +295,8 @@ def api_add_node_manually(node_data):
271295
return True
272296

273297
# GESTIÓN DE ARCHIVOS GENÉRICOS
274-
@eel.expose
275-
def api_file_save(content, name, file_type="auto"):
298+
#@eel.expose
299+
#*def api_file_save(content, name, file_type="auto"):
276300
"""
277301
Guarda archivos generados (ej. exportaciones a C, datasets).
278302
NOTA: El orden de argumentos se ajustó para coincidir con JS (content, name).
@@ -284,8 +308,8 @@ def api_file_save(content, name, file_type="auto"):
284308
except Exception as e:
285309
return {"success": False, "error": str(e)}
286310

287-
@eel.expose
288-
def api_file_list():
311+
#@eel.expose
312+
#*def api_file_list():
289313
try:
290314
file_handler.ensure_dir_exist()
291315

@@ -302,8 +326,8 @@ def api_file_list():
302326
except Exception as e:
303327
return {"error": str(e)}
304328

305-
@eel.expose
306-
def api_load_dataset_data(name):
329+
#@eel.expose
330+
#*def api_load_dataset_data(name):
307331
"""
308332
Conecta la UI con la capacidad de lectura híbrida (CSV/JSON).
309333
Permite a la interfaz previsualizar datos sin importar el formato origen.
@@ -318,8 +342,8 @@ def api_load_dataset_data(name):
318342
return {"success": False, "error": str(e)}
319343

320344
# ESTIMACIÓN DE MEMORIA
321-
@eel.expose
322-
def api_estimate_memory_desktop(model_name):
345+
#@eel.expose
346+
#*def api_estimate_memory_desktop(model_name):
323347
try:
324348
entry = ml_manager._MODEL_REGISTRY.get(model_name)
325349
if not entry:
@@ -337,8 +361,8 @@ def api_estimate_memory_desktop(model_name):
337361
return {"error": str(e)}
338362

339363
# EXPORTACIÓN A C
340-
@eel.expose
341-
def api_export_c_model(model_name):
364+
#@eel.expose
365+
#*def api_export_c_model(model_name):
342366
"""
343367
Genera el código C/C++ para Arduino del modelo entrenado y lo guarda en disco.
344368
Incluye automáticamente cabeceras y guardas para Arduino Uno.

storage/file_handler.py

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -38,17 +38,17 @@ def get_base_path():
3838

3939
# Directorios base
4040
PROJECTS_DIR = os.path.join(BASE_PATH, "projects")
41-
MODELS_DIR = os.path.join(BASE_PATH, "models")
42-
DATASETS_DIR = os.path.join(BASE_PATH, "datasets")
43-
EXPORTS_DIR = os.path.join(BASE_PATH, "exports")
41+
#MODELS_DIR = os.path.join(BASE_PATH, "models")
42+
#DATASETS_DIR = os.path.join(BASE_PATH, "datasets")
43+
#EXPORTS_DIR = os.path.join(BASE_PATH, "exports")
4444
TRASH_DIR = os.path.join(BASE_PATH, "trash")
4545

4646
# ---------------------------------------------
4747
# UTILIDADES BÁSICAS
4848

4949
def ensure_dir_exist():
5050
"""Garantiza que todas las carpetas esenciales existan."""
51-
for path in [PROJECTS_DIR, TRASH_DIR, MODELS_DIR, DATASETS_DIR, EXPORTS_DIR]:
51+
for path in [PROJECTS_DIR, TRASH_DIR]:
5252
os.makedirs(path, exist_ok=True)
5353

5454
def _get_path(directory: str, name: str, extension: str) -> str:
@@ -60,7 +60,7 @@ def _get_path(directory: str, name: str, extension: str) -> str:
6060
# -------------------------------------------------
6161
# GESTIÓN DE MODELOS (.edubotml)
6262

63-
def save_model(model_name: str, filename: str) -> bool:
63+
#*def save_model(model_name: str, filename: str) -> bool:
6464
"""
6565
Guarda un modelo entrenado (desde la memoria) al disco.
6666
"""
@@ -95,7 +95,7 @@ def save_model(model_name: str, filename: str) -> bool:
9595
print(f"[ERROR] No se pudo guardar el modelo: {e}")
9696
return False
9797

98-
def load_model(filename: str) -> Any:
98+
#*def load_model(filename: str) -> Any:
9999
"""
100100
Carga un modelo desde disco y lo registra en ml_manager.
101101
Retorna el objeto modelo instanciado.
@@ -129,7 +129,7 @@ def load_model(filename: str) -> Any:
129129
# ------------------------------------------------
130130
# GESTIÓN DE DATASETS (.json)
131131

132-
def save_dataset(data: List[List[float]], name: str) -> bool:
132+
#*def save_dataset(data: List[List[float]], name: str) -> bool:
133133
try:
134134
ensure_dir_exist()
135135
path = _get_path(DATASETS_DIR, name, ".json")
@@ -146,7 +146,7 @@ def save_dataset(data: List[List[float]], name: str) -> bool:
146146
print(f"[ERROR] Guardando dataset: {e}")
147147
return False
148148

149-
def _parse_csv_line(line: str) -> List[float]:
149+
#*def _parse_csv_line(line: str) -> List[float]:
150150
"""Helper seguro para convertir línea CSV a lista de floats."""
151151
try:
152152
# Divide por comas y limpia espacios
@@ -157,7 +157,7 @@ def _parse_csv_line(line: str) -> List[float]:
157157
# Si falla (ej: es un header 'x1,x2,y'), retorna None
158158
return None
159159

160-
def load_dataset(name: str) -> List[List[float]]:
160+
#*def load_dataset(name: str) -> List[List[float]]:
161161
"""
162162
Carga datasets soportando tanto JSON nativo como CSV importado.
163163
Prioriza CSV si se indica explícitamente o si existe el archivo.
@@ -249,7 +249,7 @@ def list_projs() -> List[str]:
249249
# -------------------------------------------------------
250250
# EXPORTACIÓN INVERSA (MODELO -> BLOQUES)
251251

252-
def export_to_blocks(model_obj: Any, model_name: str) -> Dict[str, Any]:
252+
#*def export_to_blocks(model_obj: Any, model_name: str) -> Dict[str, Any]:
253253
"""
254254
Convierte un objeto modelo en memoria a su representación visual (Bloque).
255255
Usa la nueva API de ml_exporter.
@@ -263,7 +263,7 @@ def export_to_blocks(model_obj: Any, model_name: str) -> Dict[str, Any]:
263263
# --------------------------------------------------
264264
# AUTO-EXPORT (Wrapper Genérico)
265265

266-
def auto_export(content: Any, name: str) -> bool:
266+
#*def auto_export(content: Any, name: str) -> bool:
267267
"""Detecta el tipo de contenido y lo guarda apropiadamente."""
268268
if isinstance(content, dict) and ("blocks" in content or "code" in content):
269269
return save_proj(content, name)
@@ -273,7 +273,7 @@ def auto_export(content: Any, name: str) -> bool:
273273
# Por seguridad, no guardamos objetos arbitrarios aquí.
274274
return False
275275

276-
def auto_import(path: str) -> Any:
276+
#*def auto_import(path: str) -> Any:
277277
"""Carga inteligente basada en extensión."""
278278
if path.endswith(".edubotproj"):
279279
return load_proj(os.path.basename(path).replace(".edubotproj", ""))

web/index.html

Lines changed: 53 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1113,16 +1113,62 @@ <h3 className="text-red-400 font-bold uppercase text-xs mb-3 flex items-center g
11131113
};
11141114

11151115
const trashProject = async () => {
1116+
// Caso 1: El proyecto no tiene nombre (es un borrador)
11161117
if (!currentName) {
1117-
if(confirm("El proyecto no está guardado. ¿Deseas descartar todos los cambios y limpiar el editor?")) { setNodes([]); setEdges([]); setLogs([]); addLog("Editor limpiado (Borrador descartado)", 'warn'); }
1118+
setModalConfig({
1119+
isOpen: true,
1120+
type: 'confirm',
1121+
title: 'Descartar Borrador',
1122+
message: 'El proyecto no está guardado. ¿Deseas descartar todos los cambios y limpiar el editor?',
1123+
onConfirm: () => {
1124+
setNodes([]);
1125+
setEdges([]);
1126+
setLogs([]);
1127+
addLog("Editor limpiado (Borrador descartado)", 'warn');
1128+
closeModal();
1129+
},
1130+
onCancel: closeModal
1131+
});
11181132
return;
11191133
}
1120-
if (confirm(`¿ESTÁS SEGURO?\n\nEl proyecto '${currentName}' será enviado a la Papelera de Reciclaje.\nEsta acción cerrará el editor.`)) {
1121-
try {
1122-
const res = await window.eel.api_move_to_trash(currentName, 'project')();
1123-
if (res.success) { alert("Proyecto enviado a la papelera exitosamente."); onBack(); } else { alert(`Error al eliminar: ${res.error}`); }
1124-
} catch (e) { alert(`Error de sistema: ${e}`); }
1125-
}
1134+
1135+
// Caso 2: El proyecto ya está guardado y se va a borrar de verdad
1136+
setModalConfig({
1137+
isOpen: true,
1138+
type: 'confirm',
1139+
title: 'Mover a Papelera',
1140+
message: `¿ESTÁS SEGURO?\n\nEl proyecto '${currentName}' será enviado a la Papelera de Reciclaje.\nEsta acción cerrará el editor.`,
1141+
onConfirm: async () => {
1142+
closeModal(); // Ocultamos la pregunta antes de procesar
1143+
1144+
try {
1145+
const res = await window.eel.api_move_to_trash(currentName, 'project')();
1146+
1147+
if (res.success) {
1148+
// Alerta de éxito personalizada
1149+
setModalConfig({
1150+
isOpen: true, type: 'alert', title: 'Éxito',
1151+
message: "Proyecto enviado a la papelera exitosamente.",
1152+
onConfirm: () => { closeModal(); onBack(); }
1153+
});
1154+
} else {
1155+
// Alerta de error personalizada
1156+
setModalConfig({
1157+
isOpen: true, type: 'alert', title: 'Error al eliminar',
1158+
message: res.error,
1159+
onConfirm: closeModal
1160+
});
1161+
}
1162+
} catch (e) {
1163+
setModalConfig({
1164+
isOpen: true, type: 'alert', title: 'Error de Sistema',
1165+
message: str(e),
1166+
onConfirm: closeModal
1167+
});
1168+
}
1169+
},
1170+
onCancel: closeModal
1171+
});
11261172
};
11271173

11281174
useEffect(() => { if (activeTab === 'code' && codeRef.current && window.Prism) { window.Prism.highlightElement(codeRef.current); } }, [activeTab, pythonCode]);

0 commit comments

Comments
 (0)