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.
Files changed (86) hide show
  1. package/README.md +209 -5
  2. package/package.json +15 -8
  3. package/src/assets/brand/brand.json +43 -0
  4. package/src/assets/brand/cipsa/logotypes/Logo_cipsa_borde.png +0 -0
  5. package/src/assets/brand/cipsa/logotypes/Logo_cipsa_borde.svg +9 -0
  6. package/src/assets/brand/cipsa/logotypes/Logo_cipsa_solid.png +0 -0
  7. package/src/assets/brand/cipsa/logotypes/Logo_cipsa_solid.svg +9 -0
  8. package/src/assets/brand/g360/logotypes/logo-g360-dark.svg +19 -0
  9. package/src/assets/brand/g360/logotypes/logo-g360-light.svg +19 -0
  10. package/src/assets/brand/g360/logotypes/logo_g360_dark_b64.txt +1 -0
  11. package/src/assets/brand/g360/logotypes/logo_g360_light_b64.txt +1 -0
  12. package/src/assets/config/g360-skills.json +112 -0
  13. package/src/assets/config/project-types.json +16 -0
  14. package/src/assets/config/skills.json +12 -0
  15. package/src/assets/snippets/snippets.json +293 -24
  16. package/src/assets/templates/lit-web/src/core/skill.json +12 -5
  17. package/src/assets/templates/python-cli/build-portable.bat +43 -0
  18. package/src/assets/templates/python-cli/src/core/skill.json +25 -6
  19. package/src/assets/templates/python-cli/src/main.py +107 -3
  20. package/src/assets/templates/python-customtkinter/README.md +48 -0
  21. package/src/assets/templates/python-customtkinter/assets/images/logo-g360-dark.svg +19 -0
  22. package/src/assets/templates/python-customtkinter/assets/images/logo-g360-light.svg +19 -0
  23. package/src/assets/templates/python-customtkinter/build.bat +50 -0
  24. package/src/assets/templates/python-customtkinter/pyproject.toml +26 -0
  25. package/src/assets/templates/python-customtkinter/run.bat +20 -0
  26. package/src/assets/templates/python-customtkinter/src/core/g360_theme.py +101 -0
  27. package/src/assets/templates/python-customtkinter/src/core/skill.json +35 -0
  28. package/src/assets/templates/python-customtkinter/src/main.py +103 -0
  29. package/src/assets/templates/python-flet/README.md +89 -0
  30. package/src/assets/templates/python-flet/assets/images/Logo_cipsa_borde.png +0 -0
  31. package/src/assets/templates/python-flet/assets/images/Logo_cipsa_borde.svg +9 -0
  32. package/src/assets/templates/python-flet/assets/images/Logo_cipsa_solid.png +0 -0
  33. package/src/assets/templates/python-flet/assets/images/Logo_cipsa_solid.svg +9 -0
  34. package/src/assets/templates/python-flet/assets/images/logo-g360-dark.svg +19 -0
  35. package/src/assets/templates/python-flet/assets/images/logo-g360-light.svg +19 -0
  36. package/src/assets/templates/python-flet/build.bat +36 -0
  37. package/src/assets/templates/python-flet/pyproject.toml +26 -0
  38. package/src/assets/templates/python-flet/run.bat +20 -0
  39. package/src/assets/templates/python-flet/src/core/__init__.py +0 -0
  40. package/src/assets/templates/python-flet/src/core/g360_theme.py +186 -0
  41. package/src/assets/templates/python-flet/src/core/skill.json +41 -0
  42. package/src/assets/templates/python-flet/src/export/__init__.py +0 -0
  43. package/src/assets/templates/python-flet/src/main.py +146 -0
  44. package/src/assets/templates/python-flet/src/test_app.py +107 -0
  45. package/src/assets/templates/python-flet/src/ui/__init__.py +0 -0
  46. package/src/assets/templates/python-flet-migrate/README.md +70 -0
  47. package/src/assets/templates/python-flet-migrate/assets/images/logo-g360-dark.svg +19 -0
  48. package/src/assets/templates/python-flet-migrate/assets/images/logo-g360-light.svg +19 -0
  49. package/src/assets/templates/python-flet-migrate/build.bat +36 -0
  50. package/src/assets/templates/python-flet-migrate/pyproject.toml +26 -0
  51. package/src/assets/templates/python-flet-migrate/run.bat +20 -0
  52. package/src/assets/templates/python-flet-migrate/src/core/__init__.py +0 -0
  53. package/src/assets/templates/python-flet-migrate/src/core/g360_theme.py +186 -0
  54. package/src/assets/templates/python-flet-migrate/src/core/skill.json +34 -0
  55. package/src/assets/templates/python-flet-migrate/src/main.py +97 -0
  56. package/src/assets/templates/python-flet-migrate/src/migrate_tkinter.py +56 -0
  57. package/src/assets/templates/solid-web/src/core/skill.json +12 -5
  58. package/src/assets/templates/svelte-web/src/core/skill.json +12 -5
  59. package/src/assets/templates/web-pwa/index.html +2 -7
  60. package/src/assets/templates/web-pwa/public/favicon.svg +4 -0
  61. package/src/assets/templates/web-pwa/public/manifest.json +11 -0
  62. package/src/assets/templates/web-pwa/src/App.jsx +22 -0
  63. package/src/assets/templates/web-pwa/src/components/FooterSignature.jsx +9 -0
  64. package/src/assets/templates/web-pwa/src/components/Header.jsx +12 -0
  65. package/src/assets/templates/web-pwa/src/components/LoadingOverlay.jsx +10 -0
  66. package/src/assets/templates/web-pwa/src/core/skill.json +25 -0
  67. package/src/assets/templates/web-pwa/src/main.jsx +9 -0
  68. package/src/assets/templates/web-pwa/src/styles/main.css +133 -0
  69. package/src/assets/templates/web-pwa/vite.config.js +12 -0
  70. package/src/cli.js +8 -4
  71. package/src/commands/bring.js +81 -6
  72. package/src/commands/clean.js +51 -4
  73. package/src/commands/init.js +60 -8
  74. package/src/commands/list.js +79 -9
  75. package/src/commands/signature.js +12 -8
  76. package/src/lib/asset-validator.js +145 -0
  77. package/src/lib/auditor.js +48 -0
  78. package/src/lib/global-config.js +260 -0
  79. package/src/lib/i18n.js +238 -0
  80. package/src/lib/logger.js +100 -0
  81. package/src/lib/manifest.js +70 -1
  82. package/src/lib/validator.js +39 -0
  83. package/src/schemas/skills-schema.json +1 -0
  84. package/src/schemas/snippets-schema.json +1 -0
  85. package/src/assets/templates/web-pwa/app.js +0 -1
  86. 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
+ )
@@ -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
+ }
@@ -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()
@@ -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"]