g360-cli 1.13.1 → 1.14.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 (61) hide show
  1. package/package.json +1 -1
  2. package/src/assets/config/g360-skills.json +39 -0
  3. package/src/assets/config/project-types.json +15 -4
  4. package/src/assets/templates/python-flet/README.md +6 -1
  5. package/src/assets/templates/python-flet/pyproject.toml +1 -2
  6. package/src/assets/templates/python-flet-polished/README.md +170 -0
  7. package/src/assets/templates/python-flet-polished/assets/data/sample_data.json +12 -0
  8. package/src/assets/templates/python-flet-polished/assets/fonts/Inter-Variable.ttf +0 -0
  9. package/src/assets/templates/python-flet-polished/assets/fonts/JetBrainsMono-Variable.ttf +0 -0
  10. package/src/assets/templates/python-flet-polished/assets/images/app.ico +0 -0
  11. package/src/assets/templates/python-flet-polished/assets/images/logo.svg +9 -0
  12. package/src/assets/templates/python-flet-polished/build-portable.bat +42 -0
  13. package/src/assets/templates/python-flet-polished/create_shortcut.vbs +15 -0
  14. package/src/assets/templates/python-flet-polished/g360_flet/__init__.py +0 -0
  15. package/src/assets/templates/python-flet-polished/g360_flet/g360_signature.py +210 -0
  16. package/src/assets/templates/python-flet-polished/launch.vbs +3 -0
  17. package/src/assets/templates/python-flet-polished/launch_minimized.bat +3 -0
  18. package/src/assets/templates/python-flet-polished/main.py +47 -0
  19. package/src/assets/templates/python-flet-polished/pyproject.toml +18 -0
  20. package/src/assets/templates/python-flet-polished/requirements.txt +3 -0
  21. package/src/assets/templates/python-flet-polished/run.bat +189 -0
  22. package/src/assets/templates/python-flet-polished/skill.json +28 -0
  23. package/src/assets/templates/python-flet-polished/src/__init__.py +0 -0
  24. package/src/assets/templates/python-flet-polished/src/app.py +343 -0
  25. package/src/assets/templates/python-flet-polished/src/config/__init__.py +0 -0
  26. package/src/assets/templates/python-flet-polished/src/config/theme.py +122 -0
  27. package/src/assets/templates/python-flet-polished/src/core/__init__.py +0 -0
  28. package/src/assets/templates/python-flet-polished/src/core/constants.py +49 -0
  29. package/src/assets/templates/python-flet-polished/src/core/processor.py +128 -0
  30. package/src/assets/templates/python-flet-polished/src/core/skill.json +41 -0
  31. package/src/assets/templates/python-flet-polished/src/ui/__init__.py +0 -0
  32. package/src/assets/templates/python-flet-polished/src/ui/dashboard.py +308 -0
  33. package/src/assets/templates/python-flet-polished/src/ui/kpi_card.py +69 -0
  34. package/src/assets/templates/python-flet-polished/src/ui/search_overlay.py +143 -0
  35. package/src/assets/templates/python-flet-polished/sync_portable.py +71 -0
  36. package/src/cli.js +4 -4
  37. package/src/commands/init.js +33 -2
  38. package/py/src/g360_core/__pycache__/__init__.cpython-312.pyc +0 -0
  39. package/py/src/g360_core/__pycache__/__init__.cpython-314.pyc +0 -0
  40. package/py/src/g360_core/__pycache__/batch_processor.cpython-312.pyc +0 -0
  41. package/py/src/g360_core/__pycache__/batch_processor.cpython-314.pyc +0 -0
  42. package/py/src/g360_core/__pycache__/commercial_engine.cpython-314.pyc +0 -0
  43. package/py/src/g360_core/__pycache__/logger.cpython-312.pyc +0 -0
  44. package/py/src/g360_core/__pycache__/logger.cpython-314.pyc +0 -0
  45. package/py/src/g360_core/__pycache__/pipeline.cpython-312.pyc +0 -0
  46. package/py/src/g360_core/__pycache__/pipeline.cpython-314.pyc +0 -0
  47. package/py/src/g360_core/__pycache__/processor.cpython-312.pyc +0 -0
  48. package/py/src/g360_core/__pycache__/processor.cpython-314.pyc +0 -0
  49. package/py/src/g360_core/__pycache__/processor_segmentacion.cpython-312.pyc +0 -0
  50. package/py/src/g360_core/__pycache__/processor_segmentacion.cpython-314.pyc +0 -0
  51. package/py/src/g360_core/__pycache__/processor_sku.cpython-312.pyc +0 -0
  52. package/py/src/g360_core/__pycache__/processor_sku.cpython-314.pyc +0 -0
  53. package/py/src/g360_core/__pycache__/scanner.cpython-312.pyc +0 -0
  54. package/py/src/g360_core/__pycache__/scanner.cpython-314.pyc +0 -0
  55. package/py/src/g360_core/__pycache__/utils.cpython-312.pyc +0 -0
  56. package/py/src/g360_core/__pycache__/utils.cpython-314.pyc +0 -0
  57. package/src/assets/ingestion/core/__pycache__/ingestion.cpython-312.pyc +0 -0
  58. package/src/assets/signature/g360_flet/__pycache__/__init__.cpython-312.pyc +0 -0
  59. package/src/assets/signature/g360_flet/__pycache__/__init__.cpython-314.pyc +0 -0
  60. package/src/assets/signature/g360_flet/__pycache__/g360_signature.cpython-312.pyc +0 -0
  61. package/src/assets/signature/g360_flet/__pycache__/g360_signature.cpython-314.pyc +0 -0
@@ -0,0 +1,69 @@
1
+ """
2
+ G360 KPI Card - Componente reutilizable para indicadores clave.
3
+
4
+ Patron: Card con glow backlight por color, valor numerico grande,
5
+ subtexto opcional. Clickable para abrir dialogos detallados.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ import flet as ft
10
+ from src.config.theme import rgba
11
+
12
+
13
+ class KPICard(ft.Container):
14
+ def __init__(
15
+ self,
16
+ label: str,
17
+ value: str,
18
+ color: str,
19
+ subtext: str = "",
20
+ on_click=None,
21
+ ):
22
+ super().__init__()
23
+ self._label = label
24
+ self._value = value
25
+ self._color = color
26
+ self._subtext = subtext
27
+ self._on_click = on_click
28
+
29
+ self.content = ft.Column(
30
+ controls=[
31
+ ft.Text(label, size=11, color=rgba(color, 0.8), weight=ft.FontWeight.W_500),
32
+ ft.Container(height=4),
33
+ ft.Text(
34
+ value,
35
+ size=22,
36
+ color=color,
37
+ weight=ft.FontWeight.W_700,
38
+ font_family="JetBrains Mono",
39
+ ),
40
+ ft.Container(height=2),
41
+ ft.Text(subtext, size=10, color=rgba(color, 0.6)),
42
+ ],
43
+ spacing=0,
44
+ horizontal_alignment=ft.CrossAxisAlignment.START,
45
+ )
46
+ self.padding = ft.Padding(left=14, right=14, top=10, bottom=10)
47
+ self.bgcolor = rgba(color, 0.08)
48
+ self.border_radius = 10
49
+ self.shadow = ft.BoxShadow(
50
+ spread_radius=0.5,
51
+ blur_radius=12,
52
+ color=rgba(color, 0.15),
53
+ offset=ft.Offset(0, 2),
54
+ )
55
+ self.border = ft.Border(bottom=ft.BorderSide(2, rgba(color, 0.4)))
56
+ self.cursor_type = ft.CursorType.CLICK if on_click else ft.CursorType.NONE
57
+
58
+ if on_click:
59
+ self.on_click = on_click
60
+ else:
61
+ self.on_click = None
62
+
63
+ def update_value(self, value: str, subtext: str = ""):
64
+ self._value = value
65
+ self._subtext = subtext
66
+ self.content.controls[2].value = value
67
+ if len(self.content.controls) > 3:
68
+ self.content.controls[3].value = subtext
69
+ self.update()
@@ -0,0 +1,143 @@
1
+ """
2
+ G360 Search Overlay - Buscador flotante con debounce.
3
+
4
+ Busqueda contextual en tiempo real con resultados en vivo.
5
+ Enter abre el primer resultado, Escape o click fuera cierra.
6
+ Auto-limpieza al enfocar con texto previo.
7
+ """
8
+ from __future__ import annotations
9
+
10
+ import asyncio
11
+
12
+ import flet as ft
13
+ from src.config.theme import rgba
14
+
15
+
16
+ class SearchOverlay(ft.Container):
17
+ def __init__(self, page: ft.Page, colors: dict):
18
+ super().__init__()
19
+ self.page = page
20
+ self.c = colors
21
+ self._results: list[dict] = []
22
+ self._debounce_task = None
23
+ self._selected_index = 0
24
+
25
+ self.field = ft.TextField(
26
+ hint_text="Buscar SKU, descripción, línea...",
27
+ border_radius=10,
28
+ height=44,
29
+ text_size=13,
30
+ dense=True,
31
+ border=ft.InputBorder.OUTLINE,
32
+ border_color=self.c["border"],
33
+ focused_border_color=self.c["accent"],
34
+ cursor_color=self.c["accent"],
35
+ hint_style=ft.TextStyle(size=12, color=self.c["text_muted"]),
36
+ autofocus=True,
37
+ on_change=self._on_change,
38
+ on_submit=self._on_submit,
39
+ on_focus=self._on_focus,
40
+ )
41
+
42
+ self.content = ft.Column(
43
+ controls=[
44
+ self.field,
45
+ ft.Container(
46
+ content=ft.Column([], scroll=ft.ScrollMode.AUTO, max_height=300, id="search_results"),
47
+ bgcolor=self.c["surface"],
48
+ border_radius=ft.BorderRadius.only(bottom_left=10, bottom_right=10),
49
+ border=ft.border.Top(border_color=self.c["border"], border_style=ft.BorderSide(1)),
50
+ visible=False,
51
+ padding=ft.Padding(left=0, right=0, top=0, bottom=0),
52
+ max_height=300,
53
+ height=300,
54
+ ),
55
+ ],
56
+ spacing=0,
57
+ )
58
+
59
+ self.bgcolor = self.c["surface"]
60
+ self.border_radius = 10
61
+ self.padding = ft.Padding(left=16, right=16, top=12, bottom=12)
62
+ self.width = 480
63
+ self.alignment = ft.alignment.top_center
64
+ self.margin = ft.margin.only(top=60)
65
+
66
+ def _on_focus(self, e):
67
+ if self.field.value:
68
+ self.field.value = ""
69
+ self.field.update()
70
+
71
+ async def _on_change(self, e):
72
+ query = (e.control.value or "").strip().lower()
73
+ if self._debounce_task:
74
+ self._debounce_task.cancel()
75
+ if len(query) < 2:
76
+ self._hide_results()
77
+ return
78
+ self._debounce_task = asyncio.create_task(self._debounce_search(query))
79
+
80
+ async def _debounce_search(self, query: str):
81
+ await asyncio.sleep(0.25)
82
+ self._results = await self._do_search(query)
83
+ self._show_results(self._results)
84
+
85
+ async def _do_search(self, query: str) -> list[dict]:
86
+ """Implementar en subclase. Retorna lista de dicts {sku, desc, ...}."""
87
+ return []
88
+
89
+ def _show_results(self, results: list[dict]):
90
+ if not results:
91
+ self._hide_results()
92
+ return
93
+ controls = []
94
+ for i, item in enumerate(results[:10]):
95
+ is_selected = i == self._selected_index
96
+ controls.append(
97
+ ft.Container(
98
+ content=ft.Row([
99
+ ft.Text(item.get("sku", ""), size=12, color=self.c["accent"], weight=ft.FontWeight.W_600),
100
+ ft.Container(width=8),
101
+ ft.Text(item.get("desc", item.get("descripcion", "")), size=12, color=self.c["text_primary"]),
102
+ ], spacing=0),
103
+ bgcolor=rgba(self.c["accent"], 0.1) if is_selected else "transparent",
104
+ border_radius=6,
105
+ padding=ft.Padding(left=12, right=12, top=8, bottom=8),
106
+ on_click=lambda _, r=item: self._on_select(r),
107
+ mouse_cursor=ft.CursorType.CLICK,
108
+ )
109
+ )
110
+ results_col = self.content.controls[1]
111
+ results_col.content.controls = controls
112
+ results_col.visible = True
113
+ self.update()
114
+
115
+ def _hide_results(self):
116
+ results_col = self.content.controls[1]
117
+ results_col.content.controls = []
118
+ results_col.visible = False
119
+ self.update()
120
+
121
+ def _on_submit(self, e):
122
+ if self._results:
123
+ self._on_select(self._results[self._selected_index])
124
+
125
+ def _on_select(self, item: dict):
126
+ self._hide_results()
127
+ self.page.close(self)
128
+
129
+ def did_mount(self):
130
+ """Captura Escape para cerrar."""
131
+ def on_keyboard(e: ft.KeyboardEvent):
132
+ if e.key == "Escape":
133
+ self.page.close(self)
134
+ elif e.key == "ArrowDown":
135
+ self._selected_index = min(self._selected_index + 1, len(self._results) - 1)
136
+ self._show_results(self._results)
137
+ elif e.key == "ArrowUp":
138
+ self._selected_index = max(self._selected_index - 1, 0)
139
+ self._show_results(self._results)
140
+ elif e.key == "Enter" and self._results:
141
+ self._on_select(self._results[self._selected_index])
142
+
143
+ self.page.on_keyboard_event = on_keyboard
@@ -0,0 +1,71 @@
1
+ import os
2
+ import shutil
3
+ from pathlib import Path
4
+
5
+
6
+ def sync_dir(src, dst):
7
+ if not os.path.exists(src):
8
+ return
9
+ if not os.path.exists(dst):
10
+ os.makedirs(dst)
11
+ for item in os.listdir(dst):
12
+ dst_item = os.path.join(dst, item)
13
+ src_item = os.path.join(src, item)
14
+ if not os.path.exists(src_item):
15
+ if os.path.isdir(dst_item):
16
+ shutil.rmtree(dst_item)
17
+ print(f"Eliminado directorio obsoleto: {dst_item}")
18
+ else:
19
+ os.remove(dst_item)
20
+ print(f"Eliminado archivo obsoleto: {dst_item}")
21
+ for item in os.listdir(src):
22
+ src_item = os.path.join(src, item)
23
+ dst_item = os.path.join(dst, item)
24
+ if os.path.isdir(src_item):
25
+ if item == "__pycache__":
26
+ continue
27
+ sync_dir(src_item, dst_item)
28
+ else:
29
+ if (not os.path.exists(dst_item) or
30
+ os.stat(src_item).st_mtime != os.stat(dst_item).st_mtime or
31
+ os.stat(src_item).st_size != os.stat(dst_item).st_size):
32
+ shutil.copy2(src_item, dst_item)
33
+ print(f"Sincronizado: {src_item} -> {dst_item}")
34
+
35
+
36
+ def main():
37
+ base_dir = os.path.dirname(os.path.abspath(__file__))
38
+ portable_dir = os.path.join(base_dir, "g360-app-polished-portable")
39
+
40
+ if not os.path.exists(portable_dir):
41
+ print(f"Error: No se encontro la carpeta portable '{portable_dir}'")
42
+ return
43
+
44
+ print("Iniciando sincronizacion con la version portable...")
45
+
46
+ files_to_sync = [
47
+ "main.py",
48
+ "run.bat", "launch.vbs", "launch_minimized.bat", "create_shortcut.vbs",
49
+ "build-portable.bat", "README.md",
50
+ ]
51
+ for filename in files_to_sync:
52
+ src_file = os.path.join(base_dir, filename)
53
+ dst_file = os.path.join(portable_dir, filename)
54
+ if os.path.exists(src_file):
55
+ shutil.copy2(src_file, dst_file)
56
+ print(f"Copiado: {filename} -> portable/{filename}")
57
+ else:
58
+ print(f"Omitido (no existe en origen): {filename}")
59
+
60
+ sync_dir(os.path.join(base_dir, "src"), os.path.join(portable_dir, "src"))
61
+ sync_dir(os.path.join(base_dir, "assets"), os.path.join(portable_dir, "assets"))
62
+ sync_dir(os.path.join(base_dir, "g360_flet"), os.path.join(portable_dir, "g360_flet"))
63
+
64
+ for pyc in Path(portable_dir).rglob("__pycache__"):
65
+ shutil.rmtree(pyc)
66
+
67
+ print("\nSincronizacion finalizada con exito!")
68
+
69
+
70
+ if __name__ == "__main__":
71
+ main()
package/src/cli.js CHANGED
@@ -33,7 +33,7 @@ function configAction(options) {
33
33
  console.log(chalk.gray(' positions: bottom-right, bottom-left, bottom-center, footer-right, footer-left'));
34
34
  console.log(chalk.white(' templates:'));
35
35
  console.log(chalk.gray(' web-pwa, lit-web, solid-web, svelte-web,'));
36
- console.log(chalk.gray(' python-cli, python-flet, python-flet-migrate, python-customtkinter, vba-excel'));
36
+ console.log(chalk.gray(' python-cli, python-flet-polished (default), python-flet-legacy, python-flet-migrate, python-customtkinter, vba-excel'));
37
37
  console.log(chalk.white(' skills:'));
38
38
  console.log(chalk.gray(' corporativo, corporativo-movil, corporativo-g360, corporativo-g360-movil,'));
39
39
  console.log(chalk.gray(' moderno, moderno-movil, minimalista, custom,'));
@@ -72,8 +72,8 @@ program
72
72
  program
73
73
  .command('init')
74
74
  .argument('<name>', 'Project name')
75
- .option('-t, --template <type>', 'Project template type', 'web-pwa')
76
- .option('-s, --skill <skill>', 'Skill to use (corporativo, corporativo-movil, moderno, moderno-movil, minimalista, custom, flet-desktop, flet-desktop-corporativo, cipsa, cipsa-movil)', 'corporativo-movil')
75
+ .option('-t, --template <type>', 'Project template type (default: auto segun skill — python-flet-polished para Flet)', 'auto')
76
+ .option('-s, --skill <skill>', 'Skill to use (corporativo, corporativo-movil, moderno, moderno-movil, minimalista, custom, flet-desktop, flet-desktop-corporativo, flet-desktop-polished, cipsa, cipsa-movil)', 'corporativo-movil')
77
77
  .option('-d, --dir <path>', 'Target directory', '.')
78
78
  .option('--dry-run', 'Preview without creating files')
79
79
  .option('--force', 'Overwrite existing files')
@@ -143,7 +143,7 @@ program
143
143
  program
144
144
  .command('convert')
145
145
  .argument('[path]', 'Path to convert', '.')
146
- .option('-s, --skill <skill>', 'Skill to apply (corporativo, corporativo-movil, moderno, moderno-movil, minimalista, custom, flet-desktop, flet-desktop-corporativo, cipsa, cipsa-movil)', 'corporativo-movil')
146
+ .option('-s, --skill <skill>', 'Skill to apply (corporativo, corporativo-movil, moderno, moderno-movil, minimalista, custom, flet-desktop, flet-desktop-corporativo, flet-desktop-polished, cipsa, cipsa-movil)', 'corporativo-movil')
147
147
  .option('--dry-run', 'Preview without applying')
148
148
  .option('--restructure', 'Restructure project files')
149
149
  .option('--force', 'Force dangerous changes')
@@ -10,7 +10,24 @@ import inquirer from 'inquirer';
10
10
 
11
11
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
12
12
 
13
- const PORTABLE_TEMPLATES = ['python-flet', 'python-flet-migrate', 'python-cli', 'python-customtkinter'];
13
+ const PORTABLE_TEMPLATES = ['python-flet', 'python-flet-migrate', 'python-flet-polished', 'python-cli', 'python-customtkinter'];
14
+
15
+ // Templates por defecto segun skill (mapeo automatico)
16
+ const TEMPLATE_DEFAULTS = {
17
+ 'flet-desktop': 'python-flet-polished',
18
+ 'flet-desktop-corporativo': 'python-flet-polished',
19
+ 'flet-desktop-polished': 'python-flet-polished',
20
+ 'cipsa': 'python-flet-polished',
21
+ 'cipsa-movil': 'python-flet-polished',
22
+ 'corporativo': 'web-pwa',
23
+ 'corporativo-movil': 'web-pwa',
24
+ 'corporativo-g360': 'web-pwa',
25
+ 'corporativo-g360-movil': 'web-pwa',
26
+ 'moderno': 'web-pwa',
27
+ 'moderno-movil': 'web-pwa',
28
+ 'minimalista': 'python-cli',
29
+ 'custom': 'web-pwa',
30
+ };
14
31
 
15
32
  async function askPortableOption(template) {
16
33
  if (!PORTABLE_TEMPLATES.includes(template)) {
@@ -31,7 +48,7 @@ async function askPortableOption(template) {
31
48
 
32
49
  export async function init(name, options) {
33
50
  const {
34
- template = 'web-pwa',
51
+ template: rawTemplate,
35
52
  skill = 'corporativo-movil',
36
53
  dir = '.',
37
54
  dryRun = false,
@@ -40,6 +57,20 @@ export async function init(name, options) {
40
57
  brand = false,
41
58
  } = options;
42
59
 
60
+ // Resolver template: usar default segun skill si no se especifico
61
+ let template = rawTemplate || TEMPLATE_DEFAULTS[skill] || 'web-pwa';
62
+
63
+ // Advertir si se usa template legacy
64
+ if (template === 'python-flet') {
65
+ console.log(chalk.yellow('⚠️ Template "python-flet" esta deprecado. Se usara "python-flet-polished" (estandar actual).\n'));
66
+ template = 'python-flet-polished';
67
+ }
68
+
69
+ // Si el template es auto, usar default segun skill
70
+ if (template === 'auto') {
71
+ template = TEMPLATE_DEFAULTS[skill] || 'web-pwa';
72
+ }
73
+
43
74
  const targetDir = path.join(process.cwd(), dir, name);
44
75
 
45
76
  // Leer version del CLI desde package.json