g360-cli 1.3.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +209 -5
- package/package.json +15 -8
- package/src/assets/brand/brand.json +43 -0
- package/src/assets/brand/cipsa/logotypes/Logo_cipsa_borde.png +0 -0
- package/src/assets/brand/cipsa/logotypes/Logo_cipsa_borde.svg +9 -0
- package/src/assets/brand/cipsa/logotypes/Logo_cipsa_solid.png +0 -0
- package/src/assets/brand/cipsa/logotypes/Logo_cipsa_solid.svg +9 -0
- package/src/assets/brand/g360/logotypes/logo-g360-dark.svg +19 -0
- package/src/assets/brand/g360/logotypes/logo-g360-light.svg +19 -0
- package/src/assets/brand/g360/logotypes/logo_g360_dark_b64.txt +1 -0
- package/src/assets/brand/g360/logotypes/logo_g360_light_b64.txt +1 -0
- package/src/assets/config/g360-skills.json +112 -0
- package/src/assets/config/project-types.json +16 -0
- package/src/assets/config/skills.json +12 -0
- package/src/assets/snippets/snippets.json +293 -24
- package/src/assets/templates/lit-web/src/core/skill.json +12 -5
- package/src/assets/templates/python-cli/build-portable.bat +43 -0
- package/src/assets/templates/python-cli/src/core/skill.json +25 -6
- package/src/assets/templates/python-cli/src/main.py +107 -3
- package/src/assets/templates/python-customtkinter/README.md +48 -0
- package/src/assets/templates/python-customtkinter/assets/images/logo-g360-dark.svg +19 -0
- package/src/assets/templates/python-customtkinter/assets/images/logo-g360-light.svg +19 -0
- package/src/assets/templates/python-customtkinter/build.bat +50 -0
- package/src/assets/templates/python-customtkinter/pyproject.toml +26 -0
- package/src/assets/templates/python-customtkinter/run.bat +20 -0
- package/src/assets/templates/python-customtkinter/src/core/g360_theme.py +101 -0
- package/src/assets/templates/python-customtkinter/src/core/skill.json +35 -0
- package/src/assets/templates/python-customtkinter/src/main.py +103 -0
- package/src/assets/templates/python-flet/README.md +89 -0
- package/src/assets/templates/python-flet/assets/images/Logo_cipsa_borde.png +0 -0
- package/src/assets/templates/python-flet/assets/images/Logo_cipsa_borde.svg +9 -0
- package/src/assets/templates/python-flet/assets/images/Logo_cipsa_solid.png +0 -0
- package/src/assets/templates/python-flet/assets/images/Logo_cipsa_solid.svg +9 -0
- package/src/assets/templates/python-flet/assets/images/logo-g360-dark.svg +19 -0
- package/src/assets/templates/python-flet/assets/images/logo-g360-light.svg +19 -0
- package/src/assets/templates/python-flet/build.bat +36 -0
- package/src/assets/templates/python-flet/pyproject.toml +26 -0
- package/src/assets/templates/python-flet/run.bat +20 -0
- package/src/assets/templates/python-flet/src/core/__init__.py +0 -0
- package/src/assets/templates/python-flet/src/core/g360_theme.py +186 -0
- package/src/assets/templates/python-flet/src/core/skill.json +41 -0
- package/src/assets/templates/python-flet/src/export/__init__.py +0 -0
- package/src/assets/templates/python-flet/src/main.py +146 -0
- package/src/assets/templates/python-flet/src/test_app.py +107 -0
- package/src/assets/templates/python-flet/src/ui/__init__.py +0 -0
- package/src/assets/templates/python-flet-migrate/README.md +70 -0
- package/src/assets/templates/python-flet-migrate/assets/images/logo-g360-dark.svg +19 -0
- package/src/assets/templates/python-flet-migrate/assets/images/logo-g360-light.svg +19 -0
- package/src/assets/templates/python-flet-migrate/build.bat +36 -0
- package/src/assets/templates/python-flet-migrate/pyproject.toml +26 -0
- package/src/assets/templates/python-flet-migrate/run.bat +20 -0
- package/src/assets/templates/python-flet-migrate/src/core/__init__.py +0 -0
- package/src/assets/templates/python-flet-migrate/src/core/g360_theme.py +186 -0
- package/src/assets/templates/python-flet-migrate/src/core/skill.json +34 -0
- package/src/assets/templates/python-flet-migrate/src/main.py +97 -0
- package/src/assets/templates/python-flet-migrate/src/migrate_tkinter.py +56 -0
- package/src/assets/templates/solid-web/src/core/skill.json +12 -5
- package/src/assets/templates/svelte-web/src/core/skill.json +12 -5
- package/src/assets/templates/web-pwa/index.html +2 -7
- package/src/assets/templates/web-pwa/public/favicon.svg +4 -0
- package/src/assets/templates/web-pwa/public/manifest.json +11 -0
- package/src/assets/templates/web-pwa/src/App.jsx +22 -0
- package/src/assets/templates/web-pwa/src/components/FooterSignature.jsx +9 -0
- package/src/assets/templates/web-pwa/src/components/Header.jsx +12 -0
- package/src/assets/templates/web-pwa/src/components/LoadingOverlay.jsx +10 -0
- package/src/assets/templates/web-pwa/src/core/skill.json +25 -0
- package/src/assets/templates/web-pwa/src/main.jsx +9 -0
- package/src/assets/templates/web-pwa/src/styles/main.css +133 -0
- package/src/assets/templates/web-pwa/vite.config.js +12 -0
- package/src/cli.js +8 -4
- package/src/commands/bring.js +81 -6
- package/src/commands/clean.js +51 -4
- package/src/commands/init.js +60 -8
- package/src/commands/list.js +79 -9
- package/src/commands/signature.js +12 -8
- package/src/lib/asset-validator.js +145 -0
- package/src/lib/auditor.js +48 -0
- package/src/lib/global-config.js +260 -0
- package/src/lib/i18n.js +238 -0
- package/src/lib/logger.js +100 -0
- package/src/lib/manifest.js +70 -1
- package/src/lib/validator.js +39 -0
- package/src/schemas/skills-schema.json +1 -0
- package/src/schemas/snippets-schema.json +1 -0
- package/src/assets/templates/web-pwa/app.js +0 -1
- package/src/assets/templates/web-pwa/styles.css +0 -28
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
@echo off
|
|
2
|
+
chcp 65001 >nul
|
|
3
|
+
cd /d "%~dp0"
|
|
4
|
+
|
|
5
|
+
echo.
|
|
6
|
+
echo ========================================
|
|
7
|
+
echo G360 Flet App
|
|
8
|
+
echo powered by G360
|
|
9
|
+
echo ========================================
|
|
10
|
+
echo.
|
|
11
|
+
|
|
12
|
+
REM uv run detecta/instala Python y dependencias automaticamente
|
|
13
|
+
uv run python src/main.py
|
|
14
|
+
|
|
15
|
+
if errorlevel 1 (
|
|
16
|
+
echo.
|
|
17
|
+
echo ERROR: La aplicacion fallo.
|
|
18
|
+
echo Verifique que uv este instalado: https://docs.astral.sh/uv/
|
|
19
|
+
pause
|
|
20
|
+
)
|
|
File without changes
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import base64
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class G360Theme:
|
|
7
|
+
def __init__(self, skill_path=None):
|
|
8
|
+
self._search_paths(skill_path)
|
|
9
|
+
self._load_colors()
|
|
10
|
+
self._load_effects()
|
|
11
|
+
self._load_logo()
|
|
12
|
+
|
|
13
|
+
def _search_paths(self, skill_path):
|
|
14
|
+
if skill_path and Path(skill_path).exists():
|
|
15
|
+
self.config_path = Path(skill_path)
|
|
16
|
+
return
|
|
17
|
+
candidates = [
|
|
18
|
+
Path("skill.json"),
|
|
19
|
+
Path("src/core/skill.json"),
|
|
20
|
+
Path(__file__).resolve().parent.parent.parent / "skill.json",
|
|
21
|
+
Path(__file__).resolve().parent / "skill.json",
|
|
22
|
+
Path(__file__).resolve().parent.parent / "g360" / "skills" / "flet-desktop.json",
|
|
23
|
+
]
|
|
24
|
+
for c in candidates:
|
|
25
|
+
if c.exists():
|
|
26
|
+
self.config_path = c
|
|
27
|
+
return
|
|
28
|
+
raise FileNotFoundError("skill.json not found. Run: g360 init <name> --skill flet-desktop")
|
|
29
|
+
|
|
30
|
+
def _load_colors(self):
|
|
31
|
+
with open(self.config_path, encoding="utf-8") as f:
|
|
32
|
+
self.config = json.load(f)
|
|
33
|
+
colors = self.config.get("colors", {})
|
|
34
|
+
self.bg = colors.get("bg", "#0b1220")
|
|
35
|
+
self.surface = colors.get("surface", "#1a2332")
|
|
36
|
+
self.accent = colors.get("accent", "#00d084")
|
|
37
|
+
self.text = colors.get("text", "#f0f4f8")
|
|
38
|
+
self.muted = colors.get("muted", "#94a3b8")
|
|
39
|
+
self.success = colors.get("success", "#22c55e")
|
|
40
|
+
self.warning = colors.get("warning", "#f59e0b")
|
|
41
|
+
self.error = colors.get("error", "#ef4444")
|
|
42
|
+
self.brand = self.config.get("brand", "g360")
|
|
43
|
+
|
|
44
|
+
def _load_effects(self):
|
|
45
|
+
effects = self.config.get("effects", {})
|
|
46
|
+
self.glassmorphism = effects.get("glassmorphism", True)
|
|
47
|
+
self.blur = effects.get("blur", "12px")
|
|
48
|
+
self.rounded = int(effects.get("rounded", "12px").replace("px", ""))
|
|
49
|
+
|
|
50
|
+
def _load_logo(self):
|
|
51
|
+
logo_name = None
|
|
52
|
+
guidelines = self.config.get("guidelines", {})
|
|
53
|
+
if guidelines.get("logo"):
|
|
54
|
+
logo_name = guidelines["logo"]
|
|
55
|
+
if not logo_name:
|
|
56
|
+
logo_name = "logo-g360-dark.svg"
|
|
57
|
+
paths = [
|
|
58
|
+
Path("assets/images") / logo_name,
|
|
59
|
+
Path(__file__).resolve().parent.parent.parent / "assets" / "images" / logo_name,
|
|
60
|
+
]
|
|
61
|
+
self._logo_path = None
|
|
62
|
+
for p in paths:
|
|
63
|
+
if p.exists():
|
|
64
|
+
self._logo_path = p
|
|
65
|
+
break
|
|
66
|
+
self._logo_b64 = None
|
|
67
|
+
|
|
68
|
+
def logo_src(self, page=None):
|
|
69
|
+
if self._logo_path and self._logo_path.exists():
|
|
70
|
+
return str(self._logo_path)
|
|
71
|
+
return None
|
|
72
|
+
|
|
73
|
+
def logo_base64(self):
|
|
74
|
+
if self._logo_b64:
|
|
75
|
+
return self._logo_b64
|
|
76
|
+
if self._logo_path and self._logo_path.exists():
|
|
77
|
+
raw = self._logo_path.read_bytes()
|
|
78
|
+
ext = self._logo_path.suffix.lower()
|
|
79
|
+
mime = "image/svg+xml" if ext == ".svg" else f"image/{ext[1:]}"
|
|
80
|
+
self._logo_b64 = f"data:{mime};base64,{base64.b64encode(raw).decode()}"
|
|
81
|
+
return self._logo_b64
|
|
82
|
+
brand_dir = Path(__file__).resolve().parent.parent.parent / "assets" / "images"
|
|
83
|
+
fallback = brand_dir / "logo-g360-dark.svg"
|
|
84
|
+
if fallback.exists():
|
|
85
|
+
raw = fallback.read_bytes()
|
|
86
|
+
self._logo_b64 = f"data:image/svg+xml;base64,{base64.b64encode(raw).decode()}"
|
|
87
|
+
return self._logo_b64
|
|
88
|
+
return None
|
|
89
|
+
|
|
90
|
+
def build_name(self):
|
|
91
|
+
build_cfg = self.config.get("build", {})
|
|
92
|
+
return build_cfg.get("name", "G360-App")
|
|
93
|
+
|
|
94
|
+
def logo_component(self, height=40):
|
|
95
|
+
import flet as ft
|
|
96
|
+
src = self.logo_base64()
|
|
97
|
+
if src:
|
|
98
|
+
return ft.Image(src_base64=src, height=height, fit=ft.ImageFit.CONTAIN)
|
|
99
|
+
return ft.Text(self.build_name(), size=18, weight=ft.FontWeight.BOLD, color=self.accent)
|
|
100
|
+
|
|
101
|
+
def container(self, content=None, **kwargs):
|
|
102
|
+
kwargs.setdefault("bgcolor", self.surface)
|
|
103
|
+
kwargs.setdefault("border_radius", self.rounded)
|
|
104
|
+
kwargs.setdefault("padding", 15)
|
|
105
|
+
if self.glassmorphism and "shadow" not in kwargs:
|
|
106
|
+
import flet as ft
|
|
107
|
+
kwargs["shadow"] = ft.BoxShadow(
|
|
108
|
+
spread_radius=1,
|
|
109
|
+
blur_radius=20,
|
|
110
|
+
color=f"{self.accent}20",
|
|
111
|
+
)
|
|
112
|
+
import flet as ft
|
|
113
|
+
return ft.Container(content=content, **kwargs)
|
|
114
|
+
|
|
115
|
+
def accent_button(self, text="", on_click=None, **kwargs):
|
|
116
|
+
import flet as ft
|
|
117
|
+
return ft.ElevatedButton(
|
|
118
|
+
text=text,
|
|
119
|
+
on_click=on_click,
|
|
120
|
+
style=ft.ButtonStyle(
|
|
121
|
+
bgcolor=self.accent,
|
|
122
|
+
color=self.bg,
|
|
123
|
+
shape=ft.RoundedRectangleBorder(radius=8),
|
|
124
|
+
padding=15,
|
|
125
|
+
),
|
|
126
|
+
**kwargs,
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
def styled_text(self, value="", size=14, color=None, weight=None, **kwargs):
|
|
130
|
+
import flet as ft
|
|
131
|
+
return ft.Text(
|
|
132
|
+
value=value,
|
|
133
|
+
size=size,
|
|
134
|
+
color=color or self.text,
|
|
135
|
+
weight=weight,
|
|
136
|
+
**kwargs,
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
def loading_overlay(self, message="Cargando..."):
|
|
140
|
+
import flet as ft
|
|
141
|
+
return ft.Container(
|
|
142
|
+
content=ft.Column(
|
|
143
|
+
[
|
|
144
|
+
ft.ProgressRing(width=48, height=48, color=self.accent),
|
|
145
|
+
ft.Container(height=16),
|
|
146
|
+
ft.Text(message, size=16, color=self.text),
|
|
147
|
+
],
|
|
148
|
+
horizontal_alignment=ft.CrossAxisAlignment.CENTER,
|
|
149
|
+
alignment=ft.MainAxisAlignment.CENTER,
|
|
150
|
+
),
|
|
151
|
+
bgcolor=f"{self.bg}E0",
|
|
152
|
+
expand=True,
|
|
153
|
+
alignment=ft.alignment.center,
|
|
154
|
+
visible=False,
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
def footer_signature(self):
|
|
158
|
+
import flet as ft
|
|
159
|
+
mode = self.config.get("signature", {}).get("mode", "powered")
|
|
160
|
+
text = self.config.get("signature", {}).get("text", "powered by G360")
|
|
161
|
+
return ft.Container(
|
|
162
|
+
content=ft.Row(
|
|
163
|
+
[
|
|
164
|
+
self.logo_component(height=16),
|
|
165
|
+
ft.Container(width=8),
|
|
166
|
+
ft.Text(text, size=10, color=self.muted),
|
|
167
|
+
],
|
|
168
|
+
alignment=ft.MainAxisAlignment.CENTER,
|
|
169
|
+
),
|
|
170
|
+
padding=8,
|
|
171
|
+
bgcolor=self.surface,
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
@property
|
|
175
|
+
def as_dict(self):
|
|
176
|
+
return {
|
|
177
|
+
"bg": self.bg,
|
|
178
|
+
"surface": self.surface,
|
|
179
|
+
"accent": self.accent,
|
|
180
|
+
"text": self.text,
|
|
181
|
+
"muted": self.muted,
|
|
182
|
+
"success": self.success,
|
|
183
|
+
"warning": self.warning,
|
|
184
|
+
"error": self.error,
|
|
185
|
+
"brand": self.brand,
|
|
186
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "flet-desktop",
|
|
3
|
+
"description": "G360 Flet Desktop Application",
|
|
4
|
+
"framework": "flet",
|
|
5
|
+
"version": "1.2.0",
|
|
6
|
+
"python": ">=3.11,<4.0",
|
|
7
|
+
"colors": {
|
|
8
|
+
"bg": "#0b1220",
|
|
9
|
+
"surface": "#1a2332",
|
|
10
|
+
"accent": "#00d084",
|
|
11
|
+
"text": "#f0f4f8",
|
|
12
|
+
"muted": "#94a3b8",
|
|
13
|
+
"success": "#22c55e",
|
|
14
|
+
"warning": "#f59e0b",
|
|
15
|
+
"error": "#ef4444"
|
|
16
|
+
},
|
|
17
|
+
"effects": {
|
|
18
|
+
"glassmorphism": true,
|
|
19
|
+
"blur": "12px",
|
|
20
|
+
"rounded": "12px"
|
|
21
|
+
},
|
|
22
|
+
"signature": {
|
|
23
|
+
"mode": "own",
|
|
24
|
+
"text": "G360 Desktop"
|
|
25
|
+
},
|
|
26
|
+
"build": {
|
|
27
|
+
"tool": "flet",
|
|
28
|
+
"target": "windows",
|
|
29
|
+
"name": "G360-App"
|
|
30
|
+
},
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"flet": ">=0.25.0"
|
|
33
|
+
},
|
|
34
|
+
"guidelines": {
|
|
35
|
+
"architecture": "src/core (logica), src/ui (componentes), src/export (reportes)",
|
|
36
|
+
"naming": "PascalCase para clases UI, camelCase para metodos y variables",
|
|
37
|
+
"state_management": "Usar atributos de clase para estado, evitar globales",
|
|
38
|
+
"threading": "Usar threading.Thread para operaciones bloqueantes",
|
|
39
|
+
"window_config": "width=1350, height=900, resizable=True, theme_mode=DARK"
|
|
40
|
+
}
|
|
41
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
import flet as ft
|
|
4
|
+
from core.g360_theme import G360Theme
|
|
5
|
+
|
|
6
|
+
BASE_DIR = Path(__file__).resolve().parent.parent
|
|
7
|
+
if str(BASE_DIR) not in sys.path:
|
|
8
|
+
sys.path.insert(0, str(BASE_DIR))
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class G360App:
|
|
12
|
+
def __init__(self, page: ft.Page):
|
|
13
|
+
self.page = page
|
|
14
|
+
self.theme = G360Theme()
|
|
15
|
+
self._setup_page()
|
|
16
|
+
self._build_ui()
|
|
17
|
+
|
|
18
|
+
def _setup_page(self):
|
|
19
|
+
self.page.title = self.theme.build_name()
|
|
20
|
+
self.page.theme_mode = ft.ThemeMode.DARK
|
|
21
|
+
self.page.bgcolor = self.theme.bg
|
|
22
|
+
self.page.padding = 0
|
|
23
|
+
self.page.window_width = 1350
|
|
24
|
+
self.page.window_height = 900
|
|
25
|
+
self.page.window_resizable = True
|
|
26
|
+
self.page.window_center()
|
|
27
|
+
|
|
28
|
+
def _build_ui(self):
|
|
29
|
+
self.loading = self.theme.loading_overlay()
|
|
30
|
+
self.page.add(
|
|
31
|
+
ft.Stack(
|
|
32
|
+
controls=[
|
|
33
|
+
ft.Column(
|
|
34
|
+
controls=[
|
|
35
|
+
self._build_header(),
|
|
36
|
+
ft.Container(
|
|
37
|
+
content=self._build_content(),
|
|
38
|
+
expand=True,
|
|
39
|
+
padding=20,
|
|
40
|
+
),
|
|
41
|
+
self.theme.footer_signature(),
|
|
42
|
+
],
|
|
43
|
+
spacing=0,
|
|
44
|
+
expand=True,
|
|
45
|
+
),
|
|
46
|
+
self.loading,
|
|
47
|
+
],
|
|
48
|
+
expand=True,
|
|
49
|
+
)
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
def show_loading(self, message="Procesando..."):
|
|
53
|
+
self.loading.content.controls[2].value = message
|
|
54
|
+
self.loading.visible = True
|
|
55
|
+
self.page.update()
|
|
56
|
+
|
|
57
|
+
def hide_loading(self):
|
|
58
|
+
self.loading.visible = False
|
|
59
|
+
self.page.update()
|
|
60
|
+
|
|
61
|
+
def _build_header(self):
|
|
62
|
+
return self.theme.container(
|
|
63
|
+
content=ft.Row(
|
|
64
|
+
controls=[
|
|
65
|
+
ft.Row(
|
|
66
|
+
controls=[
|
|
67
|
+
self.theme.logo_component(height=32),
|
|
68
|
+
ft.Container(width=8),
|
|
69
|
+
self.theme.styled_text(
|
|
70
|
+
"Desktop App", size=20, color=self.theme.muted
|
|
71
|
+
),
|
|
72
|
+
],
|
|
73
|
+
spacing=0,
|
|
74
|
+
),
|
|
75
|
+
ft.Container(expand=True),
|
|
76
|
+
ft.IconButton(
|
|
77
|
+
icon=ft.icons.SETTINGS,
|
|
78
|
+
on_click=lambda _: print("Settings"),
|
|
79
|
+
),
|
|
80
|
+
],
|
|
81
|
+
alignment=ft.MainAxisAlignment.START,
|
|
82
|
+
),
|
|
83
|
+
padding=20,
|
|
84
|
+
bgcolor=self.theme.surface,
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
def _build_content(self):
|
|
88
|
+
return ft.Column(
|
|
89
|
+
controls=[
|
|
90
|
+
self.theme.styled_text(
|
|
91
|
+
"Bienvenido a G360 Desktop",
|
|
92
|
+
size=24,
|
|
93
|
+
weight=ft.FontWeight.BOLD,
|
|
94
|
+
),
|
|
95
|
+
ft.Container(height=20),
|
|
96
|
+
ft.Row(
|
|
97
|
+
controls=[
|
|
98
|
+
self._build_card(
|
|
99
|
+
"Usuarios", "Gestión de usuarios", ft.icons.PEOPLE
|
|
100
|
+
),
|
|
101
|
+
self._build_card(
|
|
102
|
+
"Reportes", "Ver reportes", ft.icons.ANALYTICS
|
|
103
|
+
),
|
|
104
|
+
self._build_card(
|
|
105
|
+
"Configuración", "Ajustes del sistema", ft.icons.SETTINGS
|
|
106
|
+
),
|
|
107
|
+
],
|
|
108
|
+
spacing=20,
|
|
109
|
+
),
|
|
110
|
+
],
|
|
111
|
+
scroll=ft.ScrollMode.AUTO,
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
def _build_card(self, title, subtitle, icon):
|
|
115
|
+
return self.theme.container(
|
|
116
|
+
content=ft.Column(
|
|
117
|
+
controls=[
|
|
118
|
+
ft.Icon(icon, size=48, color=self.theme.accent),
|
|
119
|
+
self.theme.styled_text(
|
|
120
|
+
title, size=18, weight=ft.FontWeight.BOLD
|
|
121
|
+
),
|
|
122
|
+
self.theme.styled_text(
|
|
123
|
+
subtitle, size=14, color=self.theme.muted
|
|
124
|
+
),
|
|
125
|
+
],
|
|
126
|
+
horizontal_alignment=ft.CrossAxisAlignment.CENTER,
|
|
127
|
+
spacing=10,
|
|
128
|
+
),
|
|
129
|
+
width=220,
|
|
130
|
+
height=190,
|
|
131
|
+
alignment=ft.alignment.center,
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def main(page: ft.Page):
|
|
136
|
+
G360App(page)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
if __name__ == "__main__":
|
|
140
|
+
try:
|
|
141
|
+
ft.run(main)
|
|
142
|
+
except Exception as e:
|
|
143
|
+
print(f"\n[FATAL] Error al iniciar la aplicacion: {e}", flush=True)
|
|
144
|
+
import traceback
|
|
145
|
+
traceback.print_exc()
|
|
146
|
+
input("\nPresione Enter para salir...")
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
from unittest.mock import Mock, patch
|
|
3
|
+
import json
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
import flet as ft
|
|
6
|
+
from core.g360_theme import G360Theme
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class TestG360Theme(unittest.TestCase):
|
|
10
|
+
def setUp(self):
|
|
11
|
+
self.theme = G360Theme(skill_path=Path(__file__).parent / "core" / "skill.json")
|
|
12
|
+
|
|
13
|
+
def test_default_colors(self):
|
|
14
|
+
self.assertEqual(self.theme.bg, "#0b1220")
|
|
15
|
+
self.assertEqual(self.theme.surface, "#1a2332")
|
|
16
|
+
self.assertEqual(self.theme.accent, "#00d084")
|
|
17
|
+
self.assertEqual(self.theme.text, "#f0f4f8")
|
|
18
|
+
self.assertEqual(self.theme.muted, "#94a3b8")
|
|
19
|
+
self.assertEqual(self.theme.success, "#22c55e")
|
|
20
|
+
self.assertEqual(self.theme.warning, "#f59e0b")
|
|
21
|
+
self.assertEqual(self.theme.error, "#ef4444")
|
|
22
|
+
|
|
23
|
+
def test_as_dict(self):
|
|
24
|
+
d = self.theme.as_dict
|
|
25
|
+
self.assertEqual(d["bg"], "#0b1220")
|
|
26
|
+
self.assertEqual(d["surface"], "#1a2332")
|
|
27
|
+
self.assertEqual(d["accent"], "#00d084")
|
|
28
|
+
|
|
29
|
+
def test_effects(self):
|
|
30
|
+
self.assertTrue(self.theme.glassmorphism)
|
|
31
|
+
self.assertEqual(self.theme.blur, "12px")
|
|
32
|
+
self.assertEqual(self.theme.rounded, "12px")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class TestG360App(unittest.TestCase):
|
|
36
|
+
def setUp(self):
|
|
37
|
+
self.mock_page = Mock(spec=ft.Page)
|
|
38
|
+
|
|
39
|
+
def test_page_theme_setup(self):
|
|
40
|
+
self.mock_page.title = "G360 Desktop App"
|
|
41
|
+
self.mock_page.theme_mode = ft.ThemeMode.DARK
|
|
42
|
+
self.mock_page.bgcolor = "#0b1220"
|
|
43
|
+
|
|
44
|
+
self.assertEqual(self.mock_page.title, "G360 Desktop App")
|
|
45
|
+
self.assertEqual(self.mock_page.theme_mode, ft.ThemeMode.DARK)
|
|
46
|
+
self.assertEqual(self.mock_page.bgcolor, "#0b1220")
|
|
47
|
+
|
|
48
|
+
def test_colors_g360(self):
|
|
49
|
+
colors = {
|
|
50
|
+
"bg": "#0b1220",
|
|
51
|
+
"surface": "#1a2332",
|
|
52
|
+
"accent": "#00d084",
|
|
53
|
+
"text": "#f0f4f8",
|
|
54
|
+
"muted": "#94a3b8",
|
|
55
|
+
}
|
|
56
|
+
for key, value in colors.items():
|
|
57
|
+
self.assertIsNotNone(value)
|
|
58
|
+
|
|
59
|
+
def test_button_styles(self):
|
|
60
|
+
button = ft.ElevatedButton(
|
|
61
|
+
text="Test",
|
|
62
|
+
bgcolor="#00d084",
|
|
63
|
+
color="#0b1220",
|
|
64
|
+
)
|
|
65
|
+
self.assertIsNotNone(button)
|
|
66
|
+
|
|
67
|
+
def test_container_styles(self):
|
|
68
|
+
container = ft.Container(
|
|
69
|
+
bgcolor="#1a2332",
|
|
70
|
+
border_radius=12,
|
|
71
|
+
padding=20,
|
|
72
|
+
)
|
|
73
|
+
self.assertIsNotNone(container)
|
|
74
|
+
self.assertEqual(container.bgcolor, "#1a2332")
|
|
75
|
+
self.assertEqual(container.border_radius, 12)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class TestMigration(unittest.TestCase):
|
|
79
|
+
def test_convert_tkinter_widgets(self):
|
|
80
|
+
mapping = {
|
|
81
|
+
"tk.Label": "ft.Text",
|
|
82
|
+
"tk.Button": "ft.ElevatedButton",
|
|
83
|
+
"tk.Entry": "ft.TextField",
|
|
84
|
+
"tk.Frame": "ft.Container",
|
|
85
|
+
}
|
|
86
|
+
for tk, flet in mapping.items():
|
|
87
|
+
self.assertIsNotNone(tk)
|
|
88
|
+
self.assertIsNotNone(flet)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class TestCalculations(unittest.TestCase):
|
|
92
|
+
def test_sum_basic(self):
|
|
93
|
+
self.assertEqual(2 + 2, 4)
|
|
94
|
+
|
|
95
|
+
def test_list_operations(self):
|
|
96
|
+
items = [1, 2, 3, 4, 5]
|
|
97
|
+
self.assertEqual(sum(items), 15)
|
|
98
|
+
self.assertEqual(len(items), 5)
|
|
99
|
+
|
|
100
|
+
def test_dict_operations(self):
|
|
101
|
+
data = {"sku": "ABC001", "cantidad": 10, "precio": 50.0}
|
|
102
|
+
self.assertIn("sku", data)
|
|
103
|
+
self.assertEqual(data["cantidad"] * data["precio"], 500.0)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
if __name__ == "__main__":
|
|
107
|
+
unittest.main()
|
|
File without changes
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# G360 Python Flet Migration App
|
|
2
|
+
|
|
3
|
+
Template para migrar aplicaciones de **tkinter/CustomTkinter a Flet** con el ecosistema G360.
|
|
4
|
+
|
|
5
|
+
## Estructura del Proyecto
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
mi-proyecto/
|
|
9
|
+
├── src/
|
|
10
|
+
│ ├── main.py # Entry point: ft.run(main)
|
|
11
|
+
│ ├── migrate_tkinter.py # Asistente de migracion
|
|
12
|
+
│ ├── core/
|
|
13
|
+
│ │ ├── skill.json # Configuracion del skill G360
|
|
14
|
+
│ │ └── g360_theme.py # Theme engine
|
|
15
|
+
├── assets/
|
|
16
|
+
│ └── images/
|
|
17
|
+
├── skill.json # Skill G360 aplicado
|
|
18
|
+
├── pyproject.toml # Dependencias
|
|
19
|
+
├── run.bat # Ejecutar app
|
|
20
|
+
├── build.bat # Build EXE standalone
|
|
21
|
+
└── README.md
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Migracion de tkinter/CustomTkinter
|
|
25
|
+
|
|
26
|
+
### Paso 1: Reemplazar imports
|
|
27
|
+
```python
|
|
28
|
+
# Antes (tkinter)
|
|
29
|
+
import tkinter as tk
|
|
30
|
+
from tkinter import ttk, messagebox
|
|
31
|
+
|
|
32
|
+
# Despues (Flet)
|
|
33
|
+
import flet as ft
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Paso 2: Convertir widgets
|
|
37
|
+
| tkinter | Flet |
|
|
38
|
+
|---------|------|
|
|
39
|
+
| `tk.Button` | `ft.ElevatedButton` |
|
|
40
|
+
| `tk.Entry` | `ft.TextField` |
|
|
41
|
+
| `tk.Label` | `ft.Text` |
|
|
42
|
+
| `tk.Frame` | `ft.Container` |
|
|
43
|
+
| `ttk.Treeview` | `ft.DataTable` |
|
|
44
|
+
| `messagebox` | `ft.AlertDialog` |
|
|
45
|
+
|
|
46
|
+
### Paso 3: Aplicar tema G360
|
|
47
|
+
```python
|
|
48
|
+
class MigratedApp:
|
|
49
|
+
def __init__(self, page: ft.Page):
|
|
50
|
+
self.theme = G360Theme()
|
|
51
|
+
self.page = page
|
|
52
|
+
self._setup_page()
|
|
53
|
+
|
|
54
|
+
def _setup_page(self):
|
|
55
|
+
self.page.title = self.theme.build_name()
|
|
56
|
+
self.page.theme_mode = ft.ThemeMode.DARK
|
|
57
|
+
self.page.bgcolor = self.theme.bg
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Ejecucion
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
run.bat
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Build Windows
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
build.bat
|
|
70
|
+
```
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
<svg viewBox="0 0 280 70" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<defs>
|
|
3
|
+
<filter id="glow-dark" x="-40%" y="-40%" width="180%" height="180%">
|
|
4
|
+
<feGaussianBlur stdDeviation="3" result="blur" />
|
|
5
|
+
<feFlood flood-color="#22c55e" flood-opacity="0.8" result="glowColor"/>
|
|
6
|
+
<feComposite in="glowColor" in2="blur" operator="in" result="softGlow"/>
|
|
7
|
+
<feMerge>
|
|
8
|
+
<feMergeNode in="softGlow"/>
|
|
9
|
+
<feMergeNode in="SourceGraphic"/>
|
|
10
|
+
</feMerge>
|
|
11
|
+
</filter>
|
|
12
|
+
</defs>
|
|
13
|
+
<circle cx="20" cy="20" r="5" fill="#94a3b8" />
|
|
14
|
+
<circle cx="20" cy="40" r="5" fill="#22c55e" filter="url(#glow-dark)" />
|
|
15
|
+
<circle cx="20" cy="60" r="5" fill="#94a3b8" />
|
|
16
|
+
<path d="M45 20 L 65 40 L 45 60" fill="none" stroke="#22c55e" stroke-width="6" stroke-linecap="round" stroke-linejoin="round" filter="url(#glow-dark)"/>
|
|
17
|
+
<text x="85" y="45" font-family="Arial Black, sans-serif" font-weight="bold" font-size="38" fill="#f8fafc">G360</text>
|
|
18
|
+
<text x="85" y="65" font-family="monospace" font-size="12" fill="#94a3b8" letter-spacing="4">BY CCUSI</text>
|
|
19
|
+
</svg>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
<svg viewBox="0 0 280 70" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<defs>
|
|
3
|
+
<filter id="glow-light" x="-30%" y="-30%" width="160%" height="160%">
|
|
4
|
+
<feGaussianBlur stdDeviation="2.5" result="blur" />
|
|
5
|
+
<feFlood flood-color="#22c55e" flood-opacity="0.6" result="glowColor"/>
|
|
6
|
+
<feComposite in="glowColor" in2="blur" operator="in" result="softGlow"/>
|
|
7
|
+
<feMerge>
|
|
8
|
+
<feMergeNode in="softGlow"/>
|
|
9
|
+
<feMergeNode in="SourceGraphic"/>
|
|
10
|
+
</feMerge>
|
|
11
|
+
</filter>
|
|
12
|
+
</defs>
|
|
13
|
+
<circle cx="20" cy="20" r="5" fill="#475569" />
|
|
14
|
+
<circle cx="20" cy="40" r="5" fill="#22c55e" filter="url(#glow-light)" />
|
|
15
|
+
<circle cx="20" cy="60" r="5" fill="#475569" />
|
|
16
|
+
<path d="M45 20 L 65 40 L 45 60" fill="none" stroke="#22c55e" stroke-width="6" stroke-linecap="round" stroke-linejoin="round" filter="url(#glow-light)"/>
|
|
17
|
+
<text x="85" y="45" font-family="Arial Black, sans-serif" font-weight="bold" font-size="38" fill="#1e293b">G360</text>
|
|
18
|
+
<text x="85" y="65" font-family="monospace" font-size="12" fill="#64748b" letter-spacing="4">BY CCUSI</text>
|
|
19
|
+
</svg>
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
@echo off
|
|
2
|
+
chcp 65001 >nul
|
|
3
|
+
cd /d "%~dp0"
|
|
4
|
+
|
|
5
|
+
echo.
|
|
6
|
+
echo ========================================
|
|
7
|
+
echo G360 - Build Windows App
|
|
8
|
+
echo Genera ejecutable standalone
|
|
9
|
+
echo ========================================
|
|
10
|
+
echo.
|
|
11
|
+
|
|
12
|
+
REM Verificar uv
|
|
13
|
+
where uv >nul 2>&1
|
|
14
|
+
if errorlevel 1 (
|
|
15
|
+
echo Instalando uv...
|
|
16
|
+
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
REM Build con flet (maneja dependencias automaticamente)
|
|
20
|
+
echo [BUILD] Generando ejecutable...
|
|
21
|
+
uv run flet build windows --name "G360-App" --icon assets/images/app.ico
|
|
22
|
+
|
|
23
|
+
if errorlevel 1 (
|
|
24
|
+
echo.
|
|
25
|
+
echo ERROR: Fallo la generacion del ejecutable.
|
|
26
|
+
pause
|
|
27
|
+
exit /b 1
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
echo.
|
|
31
|
+
echo ========================================
|
|
32
|
+
echo BUILD EXITOSO
|
|
33
|
+
echo Ejecutable en: build/windows/
|
|
34
|
+
echo ========================================
|
|
35
|
+
echo.
|
|
36
|
+
pause
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "g360-app"
|
|
3
|
+
version = "1.0.0"
|
|
4
|
+
description = "G360 Desktop Application (Flet Migration)"
|
|
5
|
+
authors = [
|
|
6
|
+
{ name = "ccusi", email = "ccusi@cipsa.com.pe" },
|
|
7
|
+
]
|
|
8
|
+
requires-python = ">=3.11,<4.0"
|
|
9
|
+
dependencies = [
|
|
10
|
+
"flet>=0.25.0",
|
|
11
|
+
"openpyxl>=3.1.0",
|
|
12
|
+
"pandas>=2.0.0",
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
[project.optional-dependencies]
|
|
16
|
+
dev = [
|
|
17
|
+
"pytest>=8.0.0",
|
|
18
|
+
"pytest-cov>=5.0.0",
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
[build-system]
|
|
22
|
+
requires = ["hatchling"]
|
|
23
|
+
build-backend = "hatchling.build"
|
|
24
|
+
|
|
25
|
+
[tool.hatch.build.targets.wheel]
|
|
26
|
+
packages = ["src"]
|