g360-cli 1.4.0 → 1.6.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 (93) hide show
  1. package/README.md +122 -0
  2. package/package.json +11 -4
  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 +60 -0
  13. package/src/assets/signature/g360_flet/__init__.py +5 -0
  14. package/src/assets/signature/g360_flet/__pycache__/__init__.cpython-312.pyc +0 -0
  15. package/src/assets/signature/g360_flet/__pycache__/__init__.cpython-314.pyc +0 -0
  16. package/src/assets/signature/g360_flet/__pycache__/g360_signature.cpython-312.pyc +0 -0
  17. package/src/assets/signature/g360_flet/__pycache__/g360_signature.cpython-314.pyc +0 -0
  18. package/src/assets/signature/g360_flet/g360_signature.py +224 -0
  19. package/src/assets/signature/index.js +130 -0
  20. package/src/assets/snippets/snippets.json +293 -101
  21. package/src/assets/templates/lit-web/src/core/skill.json +12 -5
  22. package/src/assets/templates/python-cli/build-portable.bat +19 -9
  23. package/src/assets/templates/python-cli/src/core/skill.json +25 -6
  24. package/src/assets/templates/python-customtkinter/README.md +32 -31
  25. package/src/assets/templates/python-customtkinter/assets/images/logo-g360-dark.svg +19 -0
  26. package/src/assets/templates/python-customtkinter/assets/images/logo-g360-light.svg +19 -0
  27. package/src/assets/templates/python-customtkinter/build.bat +50 -0
  28. package/src/assets/templates/python-customtkinter/pyproject.toml +26 -0
  29. package/src/assets/templates/python-customtkinter/run.bat +11 -20
  30. package/src/assets/templates/python-customtkinter/src/core/g360_theme.py +101 -0
  31. package/src/assets/templates/python-customtkinter/src/core/skill.json +23 -10
  32. package/src/assets/templates/python-customtkinter/src/main.py +61 -81
  33. package/src/assets/templates/python-flet/README.md +52 -93
  34. package/src/assets/templates/python-flet/assets/images/Logo_cipsa_borde.png +0 -0
  35. package/src/assets/templates/python-flet/assets/images/Logo_cipsa_borde.svg +9 -0
  36. package/src/assets/templates/python-flet/assets/images/Logo_cipsa_solid.png +0 -0
  37. package/src/assets/templates/python-flet/assets/images/Logo_cipsa_solid.svg +9 -0
  38. package/src/assets/templates/python-flet/assets/images/logo-g360-dark.svg +19 -0
  39. package/src/assets/templates/python-flet/assets/images/logo-g360-light.svg +19 -0
  40. package/src/assets/templates/python-flet/build.bat +36 -0
  41. package/src/assets/templates/python-flet/pyproject.toml +26 -0
  42. package/src/assets/templates/python-flet/run.bat +11 -72
  43. package/src/assets/templates/python-flet/src/core/__init__.py +0 -0
  44. package/src/assets/templates/python-flet/src/core/g360_theme.py +193 -0
  45. package/src/assets/templates/python-flet/src/core/skill.json +28 -8
  46. package/src/assets/templates/python-flet/src/export/__init__.py +0 -0
  47. package/src/assets/templates/python-flet/src/main.py +113 -68
  48. package/src/assets/templates/python-flet/src/test_app.py +41 -37
  49. package/src/assets/templates/python-flet/src/ui/__init__.py +0 -0
  50. package/src/assets/templates/python-flet-migrate/README.md +70 -0
  51. package/src/assets/templates/python-flet-migrate/assets/images/logo-g360-dark.svg +19 -0
  52. package/src/assets/templates/python-flet-migrate/assets/images/logo-g360-light.svg +19 -0
  53. package/src/assets/templates/python-flet-migrate/build.bat +36 -0
  54. package/src/assets/templates/python-flet-migrate/pyproject.toml +26 -0
  55. package/src/assets/templates/python-flet-migrate/run.bat +20 -0
  56. package/src/assets/templates/python-flet-migrate/src/core/__init__.py +0 -0
  57. package/src/assets/templates/python-flet-migrate/src/core/g360_theme.py +186 -0
  58. package/src/assets/templates/python-flet-migrate/src/core/skill.json +34 -0
  59. package/src/assets/templates/python-flet-migrate/src/main.py +71 -42
  60. package/src/assets/templates/solid-web/src/core/skill.json +12 -5
  61. package/src/assets/templates/svelte-web/src/core/skill.json +12 -5
  62. package/src/assets/templates/web-pwa/index.html +5 -7
  63. package/src/assets/templates/web-pwa/public/favicon.svg +4 -0
  64. package/src/assets/templates/web-pwa/public/manifest.json +11 -0
  65. package/src/assets/templates/web-pwa/src/App.jsx +22 -0
  66. package/src/assets/templates/web-pwa/src/components/FooterSignature.jsx +9 -0
  67. package/src/assets/templates/web-pwa/src/components/Header.jsx +12 -0
  68. package/src/assets/templates/web-pwa/src/components/LoadingOverlay.jsx +10 -0
  69. package/src/assets/templates/web-pwa/src/core/skill.json +25 -0
  70. package/src/assets/templates/web-pwa/src/main.jsx +9 -0
  71. package/src/assets/templates/web-pwa/src/styles/main.css +133 -0
  72. package/src/assets/templates/web-pwa/vite.config.js +12 -0
  73. package/src/cli.js +10 -5
  74. package/src/commands/bring.js +81 -6
  75. package/src/commands/list.js +79 -9
  76. package/src/commands/signature.js +252 -55
  77. package/src/lib/asset-validator.js +145 -0
  78. package/src/lib/auditor.js +48 -0
  79. package/src/lib/global-config.js +260 -0
  80. package/src/lib/i18n.js +238 -0
  81. package/src/lib/logger.js +100 -0
  82. package/src/lib/manifest.js +70 -1
  83. package/src/lib/validator.js +39 -0
  84. package/src/schemas/skills-schema.json +1 -0
  85. package/src/schemas/snippets-schema.json +1 -0
  86. package/dist/g360.exe +0 -0
  87. package/src/assets/templates/python-customtkinter/build-portable.bat +0 -28
  88. package/src/assets/templates/python-customtkinter/requirements.txt +0 -1
  89. package/src/assets/templates/python-flet/build-portable.bat +0 -42
  90. package/src/assets/templates/python-flet/create_shortcut.vbs +0 -35
  91. package/src/assets/templates/python-flet/requirements.txt +0 -3
  92. package/src/assets/templates/web-pwa/app.js +0 -1
  93. package/src/assets/templates/web-pwa/styles.css +0 -28
@@ -1,101 +1,293 @@
1
- {
2
- "snippets": [
3
- {
4
- "name": "cli-argparse-basic",
5
- "description": "Basic argparse CLI structure",
6
- "language": "python",
7
- "code": "import argparse\n\nparser = argparse.ArgumentParser(description='My CLI App')\nparser.add_argument('input', help='Input file')\nparser.add_argument('-v', '--verbose', action='store_true')\nparser.add_argument('-o', '--output', default='output.txt')\nargs = parser.parse_args()"
8
- },
9
- {
10
- "name": "cli-subcommands",
11
- "description": "CLI with subcommands (git-style)",
12
- "language": "python",
13
- "code": "import argparse\n\nparser = argparse.ArgumentParser()\nsubparsers = parser.add_subparsers()\n\ninit_parser = subparsers.add_parser('init')\ninit_parser.add_argument('name')\n\nrun_parser = subparsers.add_parser('run')\nrun_parser.add_argument('--watch', action='store_true')"
14
- },
15
- {
16
- "name": "cli-logging",
17
- "description": "Logging setup for CLI",
18
- "language": "python",
19
- "code": "import logging\nimport sys\n\ndef setup_logging(verbose=False):\n level = logging.DEBUG if verbose else logging.INFO\n logging.basicConfig(\n level=level,\n format='%(asctime)s - %(levelname)s - %(message)s',\n handlers=[logging.StreamHandler(sys.stdout)]\n )\n return logging.getLogger(__name__)"
20
- },
21
- {
22
- "name": "cli-config-json",
23
- "description": "Load JSON config file",
24
- "language": "python",
25
- "code": "import json\nfrom pathlib import Path\n\ndef load_config(config_path='config.json'):\n path = Path(config_path)\n if not path.exists():\n raise FileNotFoundError(f'Config not found: {config_path}')\n with open(path) as f:\n return json.load(f)"
26
- },
27
- {
28
- "name": "cli-progress-bar",
29
- "description": "Progress bar with tqdm",
30
- "language": "python",
31
- "code": "from tqdm import tqdm\n\nfor item in tqdm(items, desc='Processing', unit='item'):\n # process item\n pass"
32
- },
33
- {
34
- "name": "cli-env-config",
35
- "description": "Environment-based configuration",
36
- "language": "python",
37
- "code": "import os\nfrom pathlib import Path\n\ndef get_config():\n env = os.getenv('APP_ENV', 'development')\n config_dir = Path(__file__).parent / 'config'\n config_file = config_dir / f'{env}.json'\n return json.loads(config_file.read_text())"
38
- },
39
- {
40
- "name": "cli-exit-codes",
41
- "description": "Exit codes constants",
42
- "language": "python",
43
- "code": "EXIT_OK = 0\nEXIT_ERROR = 1\nEXIT_USAGE = 2\nEXIT_DATA = 3\n\n# Usage: sys.exit(EXIT_ERROR)"
44
- },
45
- {
46
- "name": "g360-header",
47
- "description": "Standard G360 header",
48
- "code": "<header class='g360-header'>...</header>"
49
- },
50
- {
51
- "name": "g360-button",
52
- "description": "G360 styled button",
53
- "code": "<button class='g360-btn'>Click</button>"
54
- },
55
- {
56
- "name": "g360-card",
57
- "description": "G360 card component",
58
- "code": "<div class='g360-card'>...</div>"
59
- },
60
- {
61
- "name": "g360-badge",
62
- "description": "G360 status badge",
63
- "code": "<span class='g360-badge g360-badge-success'>Active</span>"
64
- },
65
- {
66
- "name": "flet-page",
67
- "description": "Flet page setup with G360 theme",
68
- "code": "import flet as ft\n\ndef main(page: ft.Page):\n page.title = 'G360 App'\n page.theme_mode = ft.ThemeMode.DARK\n page.bgcolor = '#0b1220'\n page.padding = 20\n page.add(ft.Text('G360 Desktop App', size=24, color='#00d084'))"
69
- },
70
- {
71
- "name": "flet-card",
72
- "description": "G360 styled Flet Card",
73
- "code": "ft.Container(\n content=ft.Column([...]),\n bgcolor='#1a2332',\n border_radius=12,\n padding=15,\n shadow=ft.BoxShadow(\n spread_radius=1,\n blur_radius=20,\n color='#00d08420'\n )\n)"
74
- },
75
- {
76
- "name": "flet-button",
77
- "description": "G360 styled Flet Button",
78
- "code": "ft.ElevatedButton(\n content=ft.Text('G360 Button', color='#0b1220'),\n style=ft.ButtonStyle(\n bgcolor='#00d084',\n color='#0b1220',\n shape=ft.RoundedRectangleBorder(radius=8),\n padding=15\n )\n)"
79
- },
80
- {
81
- "name": "flet-nav-rail",
82
- "description": "Flet navigation rail for desktop",
83
- "code": "ft.NavigationRail(\n selected_index=0,\n label_type=ft.NavigationRailLabelType.ALL,\n min_width=100,\n min_extended_width=200,\n destinations=[\n ft.NavigationRailDestination(icon=ft.icons.HOME_OUTLINED, selected_icon=ft.icons.HOME, label='Home'),\n ft.NavigationRailDestination(icon=ft.icons.SETTINGS_OUTLINED, selected_icon=ft.icons.SETTINGS, label='Settings'),\n ]\n)"
84
- },
85
- {
86
- "name": "flet-datatable",
87
- "description": "Flet DataTable with G360 styling",
88
- "code": "ft.DataTable(\n heading_row_color='#00d084',\n data_row_color='#1a2332',\n columns=[\n ft.DataColumn(ft.Text('SKU', color='#f0f4f8')),\n ft.DataColumn(ft.Text('Descripción', color='#f0f4f8')),\n ft.DataColumn(ft.Text('Cantidad', color='#f0f4f8')),\n ],\n rows=[]\n)"
89
- },
90
- {
91
- "name": "flet-dialog",
92
- "description": "Flet modal dialog",
93
- "code": "def show_dialog(page, title, content):\n dialog = ft.AlertDialog(\n title=ft.Text(title, color='#00d084'),\n content=ft.Container(content=content, padding=20),\n actions=[\n ft.TextButton('Cancelar', on_click=lambda _: page.close_dialog()),\n ft.ElevatedButton('Aceptar', bgcolor='#00d084', color='#0b1220'),\n ]\n )\n page.dialog = dialog\n dialog.open = True\n page.update()"
94
- },
95
- {
96
- "name": "flet-chart-bar",
97
- "description": "Flet bar chart for data visualization",
98
- "code": "ft.BarChart(\n bar_groups=[\n ft.BarChartGroup(\n x=0,\n bar_rods=[\n ft.BarChartRod(\n from_y=0,\n to_y=50,\n width=40,\n color='#00d084',\n ),\n ],\n ),\n ],\n left_axis=ft.ChartAxis(labels_size=40),\n bottom_axis=ft.ChartAxis(labels=[...]),\n)"
99
- }
100
- ]
101
- }
1
+ {
2
+ "snippets": [
3
+ {
4
+ "name": "cli-argparse-basic",
5
+ "description": "Basic argparse CLI structure",
6
+ "language": "python",
7
+ "code": "import argparse\n\nparser = argparse.ArgumentParser(description='My CLI App')\nparser.add_argument('input', help='Input file')\nparser.add_argument('-v', '--verbose', action='store_true')\nparser.add_argument('-o', '--output', default='output.txt')\nargs = parser.parse_args()"
8
+ },
9
+ {
10
+ "name": "cli-subcommands",
11
+ "description": "CLI with subcommands (git-style)",
12
+ "language": "python",
13
+ "code": "import argparse\n\nparser = argparse.ArgumentParser()\nsubparsers = parser.add_subparsers()\n\ninit_parser = subparsers.add_parser('init')\ninit_parser.add_argument('name')\n\nrun_parser = subparsers.add_parser('run')\nrun_parser.add_argument('--watch', action='store_true')"
14
+ },
15
+ {
16
+ "name": "cli-logging",
17
+ "description": "Logging setup for CLI",
18
+ "language": "python",
19
+ "code": "import logging\nimport sys\n\ndef setup_logging(verbose=False):\n level = logging.DEBUG if verbose else logging.INFO\n logging.basicConfig(\n level=level,\n format='%(asctime)s - %(levelname)s - %(message)s',\n handlers=[logging.StreamHandler(sys.stdout)]\n )\n return logging.getLogger(__name__)"
20
+ },
21
+ {
22
+ "name": "cli-config-json",
23
+ "description": "Load JSON config file",
24
+ "language": "python",
25
+ "code": "import json\nfrom pathlib import Path\n\ndef load_config(config_path='config.json'):\n path = Path(config_path)\n if not path.exists():\n raise FileNotFoundError(f'Config not found: {config_path}')\n with open(path) as f:\n return json.load(f)"
26
+ },
27
+ {
28
+ "name": "cli-progress-bar",
29
+ "description": "Progress bar with tqdm",
30
+ "language": "python",
31
+ "code": "from tqdm import tqdm\n\nfor item in tqdm(items, desc='Processing', unit='item'):\n # process item\n pass"
32
+ },
33
+ {
34
+ "name": "cli-env-config",
35
+ "description": "Environment-based configuration",
36
+ "language": "python",
37
+ "code": "import os\nfrom pathlib import Path\n\ndef get_config():\n env = os.getenv('APP_ENV', 'development')\n config_dir = Path(__file__).parent / 'config'\n config_file = config_dir / f'{env}.json'\n return json.loads(config_file.read_text())"
38
+ },
39
+ {
40
+ "name": "cli-exit-codes",
41
+ "description": "Exit codes constants",
42
+ "language": "python",
43
+ "code": "EXIT_OK = 0\nEXIT_ERROR = 1\nEXIT_USAGE = 2\nEXIT_DATA = 3\n\n# Usage: sys.exit(EXIT_ERROR)"
44
+ },
45
+ {
46
+ "name": "g360-header",
47
+ "description": "Standard G360 header",
48
+ "code": "<header class='g360-header'>...</header>"
49
+ },
50
+ {
51
+ "name": "g360-button",
52
+ "description": "G360 styled button",
53
+ "code": "<button class='g360-btn'>Click</button>"
54
+ },
55
+ {
56
+ "name": "g360-card",
57
+ "description": "G360 card component",
58
+ "code": "<div class='g360-card'>...</div>"
59
+ },
60
+ {
61
+ "name": "g360-badge",
62
+ "description": "G360 status badge",
63
+ "code": "<span class='g360-badge g360-badge-success'>Active</span>"
64
+ },
65
+ {
66
+ "name": "ctk-window",
67
+ "description": "CustomTkinter window setup with G360 theme",
68
+ "language": "python",
69
+ "code": "import customtkinter as ctk\nfrom core.g360_theme import G360Theme\n\nclass G360App(ctk.CTk):\n def __init__(self):\n super().__init__()\n self.theme = G360Theme()\n self.title(self.theme.build_name())\n self.geometry('900x700')\n self.configure(fg_color=self.theme.bg)\n ctk.set_appearance_mode('dark')\n ctk.set_default_color_theme('green')"
70
+ },
71
+ {
72
+ "name": "ctk-button",
73
+ "description": "G360 styled CustomTkinter button",
74
+ "language": "python",
75
+ "code": "ctk.CTkButton(\n parent,\n text='Aceptar',\n command=on_click,\n fg_color='#00d084',\n hover_color='#00b070',\n text_color='#0b1220',\n corner_radius=8,\n height=40,\n font=('Segoe UI', 14),\n)"
76
+ },
77
+ {
78
+ "name": "ctk-input",
79
+ "description": "G360 styled CustomTkinter entry",
80
+ "language": "python",
81
+ "code": "ctk.CTkEntry(\n parent,\n placeholder_text='Ingrese valor...',\n width=300,\n height=40,\n fg_color='#1a2332',\n border_color='#00d084',\n text_color='#f0f4f8',\n placeholder_text_color='#94a3b8',\n corner_radius=8,\n)"
82
+ },
83
+ {
84
+ "name": "ctk-frame",
85
+ "description": "G360 glass frame container",
86
+ "language": "python",
87
+ "code": "ctk.CTkFrame(\n parent,\n fg_color='#1a2332',\n corner_radius=12,\n border_width=1,\n border_color='#00d08440',\n)"
88
+ },
89
+ {
90
+ "name": "ctk-table",
91
+ "description": "CustomTkinter Treeview table with G360 styling",
92
+ "language": "python",
93
+ "code": "import tkinter as tk\nfrom tkinter import ttk\n\nstyle = ttk.Style()\nstyle.theme_use('clam')\nstyle.configure('G360.Treeview',\n background='#1a2332',\n foreground='#f0f4f8',\n fieldbackground='#1a2332',\n rowheight=35,\n font=('Segoe UI', 12))\nstyle.configure('G360.Treeview.Heading',\n background='#00d084',\n foreground='#0b1220',\n font=('Segoe UI', 12, 'bold'))\n\ntree = ttk.Treeview(parent, style='G360.Treeview',\n columns=('sku', 'nombre', 'cantidad'),\n show='headings')\ntree.heading('sku', text='SKU')\ntree.heading('nombre', text='Nombre')\ntree.heading('cantidad', text='Cantidad')"
94
+ },
95
+ {
96
+ "name": "ctk-dialog",
97
+ "description": "CustomTkinter message dialog",
98
+ "language": "python",
99
+ "code": "import customtkinter as ctk\n\ndef show_info(parent, title, message):\n dialog = ctk.CTkToplevel(parent)\n dialog.title(title)\n dialog.geometry('400x200')\n dialog.configure(fg_color='#0b1220')\n dialog.grab_set()\n \n ctk.CTkLabel(dialog, text=message, wraplength=350,\n text_color='#f0f4f8').pack(pady=20)\n ctk.CTkButton(dialog, text='OK',\n command=dialog.destroy,\n fg_color='#00d084').pack(pady=10)"
100
+ },
101
+ {
102
+ "name": "ctk-sidebar",
103
+ "description": "CustomTkinter sidebar navigation",
104
+ "language": "python",
105
+ "code": "class Sidebar(ctk.CTkFrame):\n def __init__(self, parent, items, on_select):\n super().__init__(parent, fg_color='#1a2332', width=200)\n self.on_select = on_select\n self.buttons = []\n \n for i, (text, icon) in enumerate(items):\n btn = ctk.CTkButton(\n self, text=text, anchor='w',\n fg_color='transparent',\n hover_color='#00d08420',\n text_color='#94a3b8',\n command=lambda idx=i: self._select(idx))\n btn.pack(fill='x', padx=10, pady=2)\n self.buttons.append(btn)\n \n def _select(self, idx):\n for i, btn in enumerate(self.buttons):\n btn.configure(\n fg_color='#00d08420' if i == idx else 'transparent',\n text_color='#00d084' if i == idx else '#94a3b8')\n self.on_select(idx)"
106
+ },
107
+ {
108
+ "name": "flet-page",
109
+ "description": "Flet page setup with G360 theme",
110
+ "code": "import flet as ft\n\ndef main(page: ft.Page):\n page.title = 'G360 App'\n page.theme_mode = ft.ThemeMode.DARK\n page.bgcolor = '#0b1220'\n page.padding = 20\n page.add(ft.Text('G360 Desktop App', size=24, color='#00d084'))"
111
+ },
112
+ {
113
+ "name": "flet-card",
114
+ "description": "G360 styled Flet Card",
115
+ "code": "ft.Container(\n content=ft.Column([...]),\n bgcolor='#1a2332',\n border_radius=12,\n padding=15,\n shadow=ft.BoxShadow(\n spread_radius=1,\n blur_radius=20,\n color='#00d08420'\n )\n)"
116
+ },
117
+ {
118
+ "name": "flet-button",
119
+ "description": "G360 styled Flet Button",
120
+ "code": "ft.ElevatedButton(\n content=ft.Text('G360 Button', color='#0b1220'),\n style=ft.ButtonStyle(\n bgcolor='#00d084',\n color='#0b1220',\n shape=ft.RoundedRectangleBorder(radius=8),\n padding=15\n )\n)"
121
+ },
122
+ {
123
+ "name": "flet-nav-rail",
124
+ "description": "Flet navigation rail for desktop",
125
+ "code": "ft.NavigationRail(\n selected_index=0,\n label_type=ft.NavigationRailLabelType.ALL,\n min_width=100,\n min_extended_width=200,\n destinations=[\n ft.NavigationRailDestination(icon=ft.icons.HOME_OUTLINED, selected_icon=ft.icons.HOME, label='Home'),\n ft.NavigationRailDestination(icon=ft.icons.SETTINGS_OUTLINED, selected_icon=ft.icons.SETTINGS, label='Settings'),\n ]\n)"
126
+ },
127
+ {
128
+ "name": "flet-datatable",
129
+ "description": "Flet DataTable with G360 styling",
130
+ "code": "ft.DataTable(\n heading_row_color='#00d084',\n data_row_color='#1a2332',\n columns=[\n ft.DataColumn(ft.Text('SKU', color='#f0f4f8')),\n ft.DataColumn(ft.Text('Descripción', color='#f0f4f8')),\n ft.DataColumn(ft.Text('Cantidad', color='#f0f4f8')),\n ],\n rows=[]\n)"
131
+ },
132
+ {
133
+ "name": "flet-dialog",
134
+ "description": "Flet modal dialog",
135
+ "code": "def show_dialog(page, title, content):\n dialog = ft.AlertDialog(\n title=ft.Text(title, color='#00d084'),\n content=ft.Container(content=content, padding=20),\n actions=[\n ft.TextButton('Cancelar', on_click=lambda _: page.close_dialog()),\n ft.ElevatedButton('Aceptar', bgcolor='#00d084', color='#0b1220'),\n ]\n )\n page.dialog = dialog\n dialog.open = True\n page.update()"
136
+ },
137
+ {
138
+ "name": "flet-chart-bar",
139
+ "description": "Flet bar chart for data visualization",
140
+ "code": "ft.BarChart(\n bar_groups=[\n ft.BarChartGroup(\n x=0,\n bar_rods=[\n ft.BarChartRod(\n from_y=0,\n to_y=50,\n width=40,\n color='#00d084',\n ),\n ],\n ),\n ],\n left_axis=ft.ChartAxis(labels_size=40),\n bottom_axis=ft.ChartAxis(labels=[...]),\n)"
141
+ },
142
+ {
143
+ "name": "flet-chart-line",
144
+ "description": "LineChart for time series data",
145
+ "code": "ft.LineChart(\n data_series=[\n ft.LineChartData(\n data_points=[\n ft.LineChartDataPoint(x=1, y=10),\n ft.LineChartDataPoint(x=2, y=25),\n ft.LineChartDataPoint(x=3, y=15),\n ft.LineChartDataPoint(x=4, y=40),\n ],\n color='#00d084',\n stroke_width=3,\n ),\n ],\n left_axis=ft.ChartAxis(labels_size=40),\n bottom_axis=ft.ChartAxis(labels=[...]),\n)"
146
+ },
147
+ {
148
+ "name": "flet-kpi-card",
149
+ "description": "Reusable KPI card for dashboards",
150
+ "code": "ft.Container(\n content=ft.Column([\n ft.Text(label, size=14, color='#94a3b8'),\n ft.Text(value, size=32, weight=ft.FontWeight.BOLD, color='#00d084'),\n ft.Row([\n ft.Icon(ft.icons.TRENDING_UP, size=16, color='#22c55e'),\n ft.Text(f'{delta}%', size=14, color='#22c55e'),\n ]) if delta else ft.Container(),\n ], spacing=5),\n bgcolor='#1a2332',\n border_radius=12,\n padding=20,\n expand=True,\n)"
151
+ },
152
+ {
153
+ "name": "flet-drop-zone",
154
+ "description": "File upload zone with FilePicker",
155
+ "code": "def __init__(self, page):\n self.page = page\n self.file_picker = ft.FilePicker(on_result=self._on_file_selected)\n self.page.overlay.append(self.file_picker)\n\ndef _build_drop_zone(self):\n return ft.Container(\n content=ft.Column([\n ft.Icon(ft.icons.UPLOAD_FILE, size=48, color='#00d084'),\n ft.Text('Arrastra un archivo o haz clic', size=16, color='#94a3b8'),\n ft.Text('Formatos: .xlsx, .xls, .csv', size=12, color='#64748b'),\n ], horizontal_alignment=ft.CrossAxisAlignment.CENTER),\n bgcolor='#1a2332',\n border=ft.border.all(2, '#00d08440'),\n border_radius=12,\n padding=40,\n on_click=lambda _: self.file_picker.pick_files(allow_multiple=False),\n ink=True,\n )\n\ndef _on_file_selected(self, e):\n if e.files:\n file = e.files[0]\n self._process_file(file.path, file.name)"
156
+ },
157
+ {
158
+ "name": "flet-progress-bar",
159
+ "description": "Styled progress bar for loading states",
160
+ "code": "ft.Container(\n content=ft.Column([\n ft.Text(label, size=14, color='#94a3b8'),\n ft.ProgressBar(\n value=progress, # 0.0 to 1.0\n bar_height=8,\n color='#00d084',\n bgcolor='#1a2332',\n ),\n ft.Text(f'{int(progress * 100)}%', size=12, color='#64748b'),\n ], spacing=5),\n padding=10,\n)"
161
+ },
162
+ {
163
+ "name": "flet-search-field",
164
+ "description": "Search field with icon",
165
+ "code": "ft.TextField(\n hint_text='Buscar...',\n prefix_icon=ft.icons.SEARCH,\n border_color='#1a2332',\n focused_border_color='#00d084',\n bgcolor='#1a2332',\n color='#f0f4f8',\n hint_style=ft.TextStyle(color='#94a3b8'),\n on_change=lambda e: on_search(e.control.value),\n)"
166
+ },
167
+ {
168
+ "name": "flet-tabs",
169
+ "description": "Tabs for multi-view layouts",
170
+ "code": "ft.Tabs(\n selected_index=0,\n animation_duration=300,\n tabs=[\n ft.Tab(text='Resumen', content=resumen_view),\n ft.Tab(text='Detalle', content=detalle_view),\n ft.Tab(text='Reportes', content=reportes_view),\n ],\n on_change=lambda e: on_tab_change(e),\n)"
171
+ },
172
+ {
173
+ "name": "flet-tooltip",
174
+ "description": "Rich tooltip on hover",
175
+ "code": "ft.Tooltip(\n message='Descripcion detallada del elemento',\n bgcolor='#1a2332',\n text_style=ft.TextStyle(color='#f0f4f8', size=12),\n padding=10,\n border_radius=8,\n wait_duration=500,\n content=ft.Text('Elemento con tooltip', color='#00d084'),\n)"
176
+ },
177
+ {
178
+ "name": "flet-pagination",
179
+ "description": "Pagination control for large tables",
180
+ "code": "def _build_pagination(self, page, total_pages, on_page_change):\n controls = []\n controls.append(ft.IconButton(ft.icons.CHEVRON_LEFT, on_click=lambda _: on_page_change(page - 1)))\n for p in range(max(0, page - 2), min(total_pages, page + 3)):\n controls.append(\n ft.Container(\n content=ft.Text(str(p + 1), size=14, color='#00d084' if p == page else '#94a3b8'),\n padding=10,\n on_click=lambda _, x=p: on_page_change(x),\n )\n )\n controls.append(ft.IconButton(ft.icons.CHEVRON_RIGHT, on_click=lambda _: on_page_change(page + 1)))\n return ft.Row(controls, alignment=ft.MainAxisAlignment.CENTER)"
181
+ },
182
+ {
183
+ "name": "flet-export-menu",
184
+ "description": "Export dropdown with PopupMenuButton",
185
+ "code": "ft.PopupMenuButton(\n content=ft.Row([\n ft.Icon(ft.icons.DOWNLOAD, color='#00d084'),\n ft.Text('Exportar', color='#00d084'),\n ]),\n items=[\n ft.PopupMenuItem(text='Exportar a Excel', icon=ft.icons.TABLE_CHART, on_click=lambda _: export_excel()),\n ft.PopupMenuItem(text='Exportar a CSV', icon=ft.icons.CSV, on_click=lambda _: export_csv()),\n ft.PopupMenuItem(text='Exportar a PDF', icon=ft.icons.PICTURE_AS_PDF, on_click=lambda _: export_pdf()),\n ft.PopupMenuDivider(),\n ft.PopupMenuItem(text='Copiar al portapapeles', icon=ft.icons.CONTENT_COPY, on_click=lambda _: copy_clipboard()),\n ],\n)"
186
+ },
187
+ {
188
+ "name": "pandas-groupby-agg",
189
+ "description": "Data aggregation with groupby and agg",
190
+ "code": "import pandas as pd\n\ndef aggregate_data(df, group_cols, agg_map):\n \"\"\"Agrega datos usando groupby + agg.\n \n Args:\n df: DataFrame\n group_cols: Lista de columnas para agrupar\n agg_map: Dict {col: agg_func}, ej: {'venta': 'sum', 'cantidad': 'mean'}\n \"\"\"\n result = df.groupby(group_cols, as_index=False).agg(agg_map)\n return result"
191
+ },
192
+ {
193
+ "name": "pandas-nc-normalize",
194
+ "description": "Normalize Notas de Credito from ERP data",
195
+ "code": "import pandas as pd\nimport numpy as np\n\ndef normalize_nc(df, monto_col='monto', tipo_col='tipo_doc'):\n \"\"\"Normaliza Notas de Credito: montos positivos y flag NC.\"\"\"\n df = df.copy()\n df['es_nc'] = df[tipo_col].str.contains('NC|NOTA CREDITO|DEVOLUCION', case=False, na=False).astype(int)\n df[monto_col] = df.apply(\n lambda r: -abs(r[monto_col]) if r['es_nc'] else abs(r[monto_col]),\n axis=1\n )\n return df"
196
+ },
197
+ {
198
+ "name": "pandas-calculate-kpi",
199
+ "description": "Calculate derived KPIs from data",
200
+ "code": "import pandas as pd\nimport numpy as np\n\ndef calculate_kpis(df, venta_col='venta_bruta', costo_col='costo', devolucion_col='devolucion'):\n \"\"\"Calcula KPIs derivados: margen, ratio devolucion, etc.\"\"\"\n result = df.copy()\n result['margen_bruto'] = result[venta_col] - result[costo_col]\n result['margen_pct'] = np.where(\n result[venta_col] != 0,\n (result['margen_bruto'] / result[venta_col]) * 100,\n 0\n )\n if devolucion_col in result.columns:\n result['ratio_devolucion'] = np.where(\n result[venta_col] != 0,\n (abs(result[devolucion_col]) / result[venta_col]) * 100,\n 0\n )\n return result"
201
+ },
202
+ {
203
+ "name": "pandas-date-range",
204
+ "description": "Filter DataFrame by date range",
205
+ "code": "import pandas as pd\n\ndef filter_by_date_range(df, date_col, start_date, end_date):\n \"\"\"Filtra DataFrame por rango de fechas.\"\"\"\n mask = (df[date_col] >= pd.Timestamp(start_date)) & (df[date_col] <= pd.Timestamp(end_date))\n return df[mask].copy()\n\n# Uso:\n# df_filtrado = filter_by_date_range(df, 'fecha', '2025-01-01', '2025-12-31')"
206
+ },
207
+ {
208
+ "name": "pandas-gap-analysis",
209
+ "description": "Gap analysis between catalog and purchases",
210
+ "code": "import pandas as pd\n\ndef gap_analysis(catalogo, compras, key_col='sku'):\n \"\"\"Analiza gaps entre catalogo y compras.\n \n Returns:\n Dict con: en_catalogo, en_compras, en_ambos, sin_compras, no_catalogados\n \"\"\"\n cat_set = set(catalogo[key_col].unique())\n comp_set = set(compras[key_col].unique())\n return {\n 'en_catalogo': len(cat_set),\n 'en_compras': len(comp_set),\n 'en_ambos': len(cat_set & comp_set),\n 'sin_compras': len(cat_set - comp_set),\n 'no_catalogados': len(comp_set - cat_set),\n }"
211
+ },
212
+ {
213
+ "name": "pandas-temporal-compare",
214
+ "description": "Temporal comparison A vs B period",
215
+ "code": "import pandas as pd\n\ndef temporal_compare(df, date_col, value_col, group_col, period_a, period_b):\n \"\"\"Compara metricas entre dos periodos.\n \n Args:\n period_a, period_b: Tuplas (start, end) como strings 'YYYY-MM-DD'\n \"\"\"\n a = df[(df[date_col] >= period_a[0]) & (df[date_col] <= period_a[1])]\n b = df[(df[date_col] >= period_b[0]) & (df[date_col] <= period_b[1])]\n \n a_agg = a.groupby(group_col)[value_col].sum().rename('periodo_a')\n b_agg = b.groupby(group_col)[value_col].sum().rename('periodo_b')\n \n result = pd.concat([a_agg, b_agg], axis=1).fillna(0)\n result['variacion'] = result['periodo_b'] - result['periodo_a']\n result['variacion_pct'] = (result['variacion'] / result['periodo_a'].replace(0, float('nan'))) * 100\n return result"
216
+ },
217
+ {
218
+ "name": "pandas-hhi-index",
219
+ "description": "Calculate Herfindahl-Hirschman Index",
220
+ "code": "import pandas as pd\nimport numpy as np\n\ndef calculate_hhi(df, group_col, value_col):\n \"\"\"Calcula el indice HHI de concentracion.\n \n HHI = sum((share_i)^2) donde share_i es proporcion del total.\n > 2500: Alta concentracion\n 1500-2500: Concentracion moderada\n < 1500: Baja concentracion\n \"\"\"\n total = df[value_col].sum()\n if total == 0:\n return 0.0\n shares = df.groupby(group_col)[value_col].sum() / total\n hhi = (shares ** 2).sum() * 10000\n return round(hhi, 2)"
221
+ },
222
+ {
223
+ "name": "openpyxl-workbook",
224
+ "description": "Create Excel workbook with G360 styling",
225
+ "code": "from openpyxl import Workbook\nfrom openpyxl.styles import Font, PatternFill, Alignment, Border, Side\n\nG360_ACCENT = '00D084'\nG360_SURFACE = '1A2332'\nG360_TEXT = 'F0F4F8'\n\ndef create_g360_workbook():\n wb = Workbook()\n ws = wb.active\n ws.title = 'Reporte G360'\n \n # Header styling\n header_fill = PatternFill(start_color=G360_ACCENT, end_color=G360_ACCENT, fill_type='solid')\n header_font = Font(bold=True, color=G360_TEXT, size=11)\n thin_border = Border(\n left=Side(style='thin', color=G360_SURFACE),\n right=Side(style='thin', color=G360_SURFACE),\n top=Side(style='thin', color=G360_SURFACE),\n bottom=Side(style='thin', color=G360_SURFACE),\n )\n \n return wb, ws, header_fill, header_font, thin_border"
226
+ },
227
+ {
228
+ "name": "openpyxl-styled-table",
229
+ "description": "Write styled table to Excel sheet",
230
+ "code": "def write_styled_table(ws, headers, data, header_fill, header_font, thin_border):\n \"\"\"Escribe una tabla con headers estilizados G360.\"\"\"\n # Headers\n for col_idx, header in enumerate(headers, 1):\n cell = ws.cell(row=1, column=col_idx, value=header)\n cell.fill = header_fill\n cell.font = header_font\n cell.alignment = Alignment(horizontal='center')\n cell.border = thin_border\n \n # Data\n for row_idx, row_data in enumerate(data, 2):\n for col_idx, value in enumerate(row_data, 1):\n cell = ws.cell(row=row_idx, column=col_idx, value=value)\n cell.border = thin_border\n cell.alignment = Alignment(horizontal='center')\n \n # Auto-width\n for col in ws.columns:\n max_length = max(len(str(cell.value or '')) for cell in col)\n ws.column_dimensions[col[0].column_letter].width = max_length + 3"
231
+ },
232
+ {
233
+ "name": "openpyxl-kpi-sheet",
234
+ "description": "Create KPI summary sheet with formatting",
235
+ "code": "def write_kpi_sheet(wb, sheet_name, kpis):\n \"\"\"Crea un sheet de KPIs con formato G360.\n \n kpis: [(nombre, valor, formato), ...]\n \"\"\"\n ws = wb.create_sheet(title=sheet_name)\n \n accent_fill = PatternFill(start_color='00D084', end_color='00D084', fill_type='solid')\n accent_font = Font(bold=True, color='F0F4F8', size=14)\n value_font = Font(bold=True, size=20, color='00D084')\n \n ws.cell(row=1, column=1, value='KPI').fill = accent_fill\n ws.cell(row=1, column=1).font = accent_font\n ws.cell(row=1, column=2, value='Valor').fill = accent_fill\n ws.cell(row=1, column=2).font = accent_font\n \n for i, (name, value, fmt) in enumerate(kpis, 2):\n ws.cell(row=i, column=1, value=name)\n ws.cell(row=i, column=2, value=value)\n ws.cell(row=i, column=2).font = value_font\n if fmt:\n ws.cell(row=i, column=2).number_format = fmt\n \n ws.column_dimensions['A'].width = 30\n ws.column_dimensions['B'].width = 20"
236
+ },
237
+ {
238
+ "name": "openpyxl-multi-sheet",
239
+ "description": "Create multi-sheet Excel report",
240
+ "code": "def create_multi_sheet_report(output_path, sheets_data):\n \"\"\"Crea un Excel con multiples sheets.\n \n sheets_data: [(name, headers, rows), ...]\n \"\"\"\n from openpyxl import Workbook\n wb = Workbook()\n wb.remove(wb.active)\n \n header_fill = PatternFill(start_color='00D084', end_color='00D084', fill_type='solid')\n header_font = Font(bold=True, color='F0F4F8', size=11)\n \n for name, headers, rows in sheets_data:\n ws = wb.create_sheet(title=name[:31])\n for col_idx, h in enumerate(headers, 1):\n cell = ws.cell(row=1, column=col_idx, value=h)\n cell.fill = header_fill\n cell.font = header_font\n for row_idx, row in enumerate(rows, 2):\n for col_idx, val in enumerate(row, 1):\n ws.cell(row=row_idx, column=col_idx, value=val)\n \n wb.save(output_path)\n return output_path"
241
+ },
242
+ {
243
+ "name": "flet-loading-overlay",
244
+ "description": "Loading overlay with spinner for blocking operations",
245
+ "code": "self.loading = ft.Container(\n content=ft.Column([\n ft.ProgressRing(width=48, height=48, color='#00d084'),\n ft.Container(height=16),\n ft.Text('Cargando...', size=16, color='#f0f4f8'),\n ], horizontal_alignment=ft.CrossAxisAlignment.CENTER, alignment=ft.MainAxisAlignment.CENTER),\n bgcolor='#0b1220E0',\n expand=True,\n alignment=ft.alignment.center,\n visible=False,\n)\n\n# En el layout, dentro de un Stack:\nft.Stack([page_content, self.loading], expand=True)\n\ndef show_loading(self, message='Procesando...'):\n self.loading.content.controls[2].value = message\n self.loading.visible = True\n self.page.update()\n\ndef hide_loading(self):\n self.loading.visible = False\n self.page.update()"
246
+ },
247
+ {
248
+ "name": "flet-logo-base64",
249
+ "description": "Embed logo as base64 (PyInstaller-safe, no file paths)",
250
+ "code": "import base64\nfrom pathlib import Path\n\nLOGO_B64 = None\n\ndef get_logo_src():\n global LOGO_B64\n if LOGO_B64:\n return LOGO_B64\n candidates = [\n Path('assets/images/logo-g360-dark.svg'),\n Path(__file__).parent / 'assets' / 'images' / 'logo-g360-dark.svg',\n ]\n for p in candidates:\n if p.exists():\n raw = p.read_bytes()\n b64 = base64.b64encode(raw).decode()\n LOGO_B64 = f'data:image/svg+xml;base64,{b64}'\n return LOGO_B64\n return None\n\n# Uso en Flet:\n# ft.Image(src_base64=get_logo_src(), height=40, fit=ft.ImageFit.CONTAIN)"
251
+ },
252
+ {
253
+ "name": "flet-logging-setup",
254
+ "description": "Logging setup for desktop apps (debug remoto)",
255
+ "code": "import logging\nimport sys\nfrom pathlib import Path\n\ndef setup_logging(app_name='g360-app'):\n log_dir = Path.home() / '.g360' / 'logs'\n log_dir.mkdir(parents=True, exist_ok=True)\n log_file = log_dir / f'{app_name}.log'\n \n logging.basicConfig(\n level=logging.DEBUG,\n format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',\n handlers=[\n logging.FileHandler(log_file, encoding='utf-8'),\n logging.StreamHandler(sys.stdout),\n ]\n )\n return logging.getLogger(app_name)\n\n# Uso:\n# logger = setup_logging('mi-app')\n# logger.info('App iniciada')"
256
+ },
257
+ {
258
+ "name": "flet-footer-signature",
259
+ "description": "G360 signature footer with logo + powered by",
260
+ "code": "ft.Container(\n content=ft.Row([\n ft.Image(src_base64=get_logo_src(), height=16, fit=ft.ImageFit.CONTAIN),\n ft.Container(width=8),\n ft.Text('powered by G360', size=10, color='#94a3b8'),\n ], alignment=ft.MainAxisAlignment.CENTER),\n padding=8,\n bgcolor='#1a2332',\n)"
261
+ },
262
+ {
263
+ "name": "flet-error-boundary",
264
+ "description": "Global error handler for Flet callbacks",
265
+ "code": "import traceback\nimport logging\n\nlogger = logging.getLogger('g360-app')\n\ndef safe_handler(fn):\n \"\"\"Decorator que atrapa excepciones en callbacks Flet.\"\"\"\n def wrapper(*args, **kwargs):\n try:\n return fn(*args, **kwargs)\n except Exception as e:\n logger.error(f'Error en {fn.__name__}: {e}\\n{traceback.format_exc()}')\n page = args[0].page if hasattr(args[0], 'page') else None\n if page:\n page.snack_bar = ft.SnackBar(\n content=ft.Text(f'Error: {str(e)[:80]}'),\n bgcolor='#ef4444',\n )\n page.snack_bar.open = True\n page.update()\n return wrapper\n\n# Uso:\n# @safe_handler\n# def on_click(e): ..."
266
+ },
267
+ {
268
+ "name": "flet-snackbar-error",
269
+ "description": "Show error message via SnackBar",
270
+ "code": "def show_error(page, message):\n page.snack_bar = ft.SnackBar(\n content=ft.Row([\n ft.Icon(ft.icons.ERROR_OUTLINE, color='#f0f4f8', size=20),\n ft.Text(str(message)[:120], color='#f0f4f8', size=14),\n ]),\n bgcolor='#ef4444',\n duration=5000,\n )\n page.snack_bar.open = True\n page.update()\n\ndef show_success(page, message):\n page.snack_bar = ft.SnackBar(\n content=ft.Row([\n ft.Icon(ft.icons.CHECK_CIRCLE, color='#0b1220', size=20),\n ft.Text(message, color='#0b1220', size=14),\n ]),\n bgcolor='#22c55e',\n duration=3000,\n )\n page.snack_bar.open = True\n page.update()\n\ndef show_warning(page, message):\n page.snack_bar = ft.SnackBar(\n content=ft.Row([\n ft.Icon(ft.icons.WARNING, color='#0b1220', size=20),\n ft.Text(message, color='#0b1220', size=14),\n ]),\n bgcolor='#f59e0b',\n duration=4000,\n )\n page.snack_bar.open = True\n page.update()"
271
+ },
272
+ {
273
+ "name": "openpyxl-metadata",
274
+ "description": "G360 standard author (ccusi) + metadata on openpyxl workbook",
275
+ "code": "from openpyxl import Workbook\n\nwb = Workbook()\n# G360 standard metadata - autor consistente en todo el ecosistema\nwb.properties.creator = \"ccusi\"\nwb.properties.title = \"<APP_NAME>\"\nwb.properties.description = \"Generado por G360\"\n\nws = wb.active\n# ... populate data ...\nwb.save(\"reporte.xlsx\")"
276
+ },
277
+ {
278
+ "name": "openpyxl-excel-table",
279
+ "description": "Create Excel Table with formulas, totals outside sort range",
280
+ "code": "from openpyxl.worksheet.table import Table, TableStyleInfo\n\ndef write_table_with_formulas(ws, headers, data, total_values):\n \"\"\"Escribe datos como Tabla Excel con formulas vivas.\n \n La fila TOTAL queda fuera de la tabla para que el ordenamiento\n no la mueva. Los porcentajes usan =IFERROR(D{row}/D$total, 0).\n \"\"\"\n n_data = len(data)\n total_row = n_data + 2\n ncols = len(headers)\n \n # Headers\n for ci, h in enumerate(headers, 1):\n ws.cell(row=1, column=ci, value=h)\n \n # Data\n for ri, row in enumerate(data, 2):\n for ci, val in enumerate(row, 1):\n ws.cell(row=ri, column=ci, value=val)\n \n # Total row (FUERA del rango de la tabla)\n ws.cell(row=total_row, column=1, value=\"TOTAL\")\n ws.cell(row=total_row, column=4).value = f\"=SUM(D2:D{n_data+1})\"\n ws.cell(row=total_row, column=5).value = f\"=SUM(E2:E{n_data+1})\"\n ws.cell(row=total_row, column=6).value = 1.0\n ws.cell(row=total_row, column=7).value = 1.0\n \n # Live percentage formulas (IFERROR para evitar #DIV/0!)\n for ri in range(2, n_data + 2):\n ws.cell(row=ri, column=6).value = f\"=IFERROR(D{ri}/D${total_row}, 0)\"\n ws.cell(row=ri, column=7).value = f\"=IFERROR(E{ri}/E${total_row}, 0)\"\n \n # Crear tabla Excel (solos datos, sin total)\n col_letter = get_column_letter(ncols)\n table_ref = f\"A1:{col_letter}{n_data+1}\"\n table_name = f\"tbl_data\"\n tab = Table(displayName=table_name, ref=table_ref)\n tab.tableStyleInfo = TableStyleInfo(\n name=\"TableStyleMedium9\",\n showFirstColumn=False,\n showLastColumn=False,\n showRowStripes=True,\n showColumnStripes=False,\n )\n ws.add_table(tab)"
281
+ },
282
+ {
283
+ "name": "flet-export-progress",
284
+ "description": "Progress bars per vendor with on_file_done callback",
285
+ "code": "# En consolidate_view.py:_exportar()\ndef _exportar(self):\n vendedores_info = [\n (vid, vendedor.nom_vendedor)\n for vid, vendedor in self.vendedores.items()\n if self.seleccion.get(vid, [])\n ]\n if not vendedores_info:\n return\n\n self._prog_rows.clear()\n for _, nombre in vendedores_info:\n bar = ft.ProgressBar(width=380, color=self.p.accent, value=0)\n row = ft.Row([\n ft.Text(nombre, size=12, color=self.p.text, width=160),\n bar,\n ], spacing=8, vertical_alignment=ft.CrossAxisAlignment.CENTER)\n self._prog_rows[nombre] = row\n\n self._prog_col.controls = list(self._prog_rows.values())\n self._prog_col.visible = True\n self.status.value = \"Exportando...\"\n self.page.update()\n\n def on_file_done(nombre: str):\n row = self._prog_rows.get(nombre)\n if row:\n row.controls[1].value = 1.0\n self.page.update()\n\n def task():\n try:\n paths = exportar_xlsx(\n ...,\n on_file_done=on_file_done,\n )\n self.status.value = f\"Reportes: {len(paths)} archivo(s)\"\n self.status.color = self.p.success\n self.page.update()\n except Exception as ex:\n self.status.value = f\"Error: {str(ex)}\"\n\n threading.Thread(target=task, daemon=True).start()\n\n# En exporter.py: agregar callback en el signature\ndef exportar_xlsx(...,\n on_file_done: Optional[callable] = None) -> list[str]:\n ...\n for vid, vendedor in por_vendedor.items():\n ...\n generados.append(fpath)\n if on_file_done:\n on_file_done(vendedor.nom_vendedor)\n return generados"
286
+ },
287
+ {
288
+ "name": "flet-glass-theme",
289
+ "description": "Modern glassmorphism theme with G360 accent",
290
+ "code": "# theme.py - Paleta con glassmorphism\nfrom dataclasses import dataclass\n\n@dataclass\nclass Paleta:\n bg: str\n surface: str\n surface_hover: str\n accent: str\n accent_secondary: str\n text: str\n text_secondary: str\n border: str\n success: str\n warning: str\n danger: str\n info: str\n glass_bg: str\n glass_border: str\n input_bg: str\n\nLIGHT = Paleta(\n bg=\"#f0f4f8\", surface=\"#ffffff\", surface_hover=\"#f8fafc\",\n accent=\"#00d084\", accent_secondary=\"#6366f1\",\n text=\"#0b1221\", text_secondary=\"#64748b\",\n border=\"#e2e8f0\", success=\"#10b981\", warning=\"#f59e0b\",\n danger=\"#ef4444\", info=\"#06b6d4\",\n glass_bg=\"rgba(255,255,255,0.65)\",\n glass_border=\"rgba(226,232,240,0.6)\",\n input_bg=\"#f1f5f9\",\n)\n\nDARK = Paleta(\n bg=\"#0b0f19\", surface=\"#161b2b\", surface_hover=\"#1e2438\",\n accent=\"#00d084\", accent_secondary=\"#818cf8\",\n text=\"#f1f5f9\", text_secondary=\"#94a3b8\",\n border=\"#1e293b\", success=\"#34d399\", warning=\"#fbbf24\",\n danger=\"#f87171\", info=\"#22d3ee\",\n glass_bg=\"rgba(22,27,43,0.75)\",\n glass_border=\"rgba(30,41,59,0.6)\",\n input_bg=\"#1e2438\",\n)\n\n# Uso en containers (efecto glass):\n# ft.Container(\n# bgcolor=self.p.glass_bg,\n# border=ft.BorderSide(1, self.p.glass_border),\n# blur=ft.Blur(12, 12),\n# border_radius=10,\n# padding=16,\n# )"
291
+ }
292
+ ]
293
+ }
@@ -1,15 +1,22 @@
1
1
  {
2
- "skill": "corporativo",
3
- "device": "movil",
4
- "template": "lit-web",
2
+ "name": "lit-web",
3
+ "description": "G360 Lit Web Components Application",
4
+ "framework": "lit",
5
5
  "version": "1.0.0",
6
- "createdAt": "2024-01-01T00:00:00.000Z",
7
6
  "colors": {
8
7
  "bg": "#0b1220",
9
8
  "surface": "#151e2e",
10
9
  "accent": "#00d084",
11
10
  "text": "#f0f4f8",
12
- "muted": "#94a3b8"
11
+ "muted": "#94a3b8",
12
+ "success": "#22c55e",
13
+ "warning": "#f59e0b",
14
+ "error": "#ef4444"
15
+ },
16
+ "effects": {
17
+ "glassmorphism": true,
18
+ "blur": "12px",
19
+ "rounded": "12px"
13
20
  },
14
21
  "signature": {
15
22
  "mode": "powered",
@@ -5,29 +5,39 @@ title G360 - Portable CLI Launcher
5
5
  echo.
6
6
  echo ==============================================
7
7
  echo G360 CLI - Portable Version
8
- echo (Requires Python installed)
8
+ echo (Requires Python + uv installed)
9
9
  echo ==============================================
10
10
  echo.
11
11
 
12
- python --version >nul 2>&1
12
+ where uv >nul 2>&1
13
13
  if errorlevel 1 (
14
- echo ERROR: Python no encontrado
15
- echo Instala Python desde: https://python.org
16
- echo O usa setup.bat para configurar ambiente
14
+ echo [SETUP] uv no encontrado
15
+ echo.
16
+ echo Por favor instala uv desde:
17
+ echo https://github.com/astral-sh/uv
18
+ echo.
17
19
  pause
18
20
  exit /b 1
19
21
  )
20
22
 
21
- echo Buscando archivo principal...
23
+ echo [CHECK] uv: OK
24
+
25
+ if not exist ".venv" (
26
+ echo [SETUP] Creando entorno virtual...
27
+ uv venv
28
+ )
29
+
30
+ echo [SETUP] Sincronizando dependencias...
31
+ uv sync
32
+
22
33
  if not exist "src\main.py" (
23
34
  echo ERROR: src\main.py no encontrado
24
35
  pause
25
36
  exit /b 1
26
37
  )
27
38
 
28
- echo Ejecutando G360 CLI...
29
39
  echo.
30
- python src\main.py %*
40
+ echo [RUN] Ejecutando G360 CLI...
41
+ uv run python src\main.py %*
31
42
 
32
- echo.
33
43
  pause
@@ -1,6 +1,25 @@
1
- """
2
- G360 Python Skill Configuration
3
- """
4
- SKILL_NAME = "python-cli-template"
5
- SKILL_VERSION = "1.0.0"
6
- G360_BRAND = "G360 Ecosystem"
1
+ {
2
+ "name": "python-cli",
3
+ "description": "G360 Python CLI Application",
4
+ "framework": "python-cli",
5
+ "version": "1.0.0",
6
+ "colors": {
7
+ "bg": "#0b1220",
8
+ "surface": "#1a2332",
9
+ "accent": "#00d084",
10
+ "text": "#f0f4f8",
11
+ "muted": "#94a3b8",
12
+ "success": "#22c55e",
13
+ "warning": "#f59e0b",
14
+ "error": "#ef4444"
15
+ },
16
+ "effects": {
17
+ "glassmorphism": false,
18
+ "blur": "0px",
19
+ "rounded": "8px"
20
+ },
21
+ "signature": {
22
+ "mode": "powered",
23
+ "text": "powered by G360"
24
+ }
25
+ }
@@ -1,47 +1,48 @@
1
- # G360 CustomTkinter Template
1
+ # G360 Python CustomTkinter App
2
2
 
3
- Modern GUI desktop application with CustomTkinter and G360 theming.
3
+ ## Estructura
4
+
5
+ ```
6
+ mi-proyecto/
7
+ ├── src/
8
+ │ ├── main.py # Entry point
9
+ │ └── core/
10
+ │ ├── skill.json # Configuracion G360
11
+ │ └── g360_theme.py # Theme engine
12
+ ├── assets/images/
13
+ ├── skill.json
14
+ ├── pyproject.toml
15
+ ├── run.bat # Ejecutar app
16
+ ├── build.bat # Build EXE (PyInstaller)
17
+ └── README.md
18
+ ```
4
19
 
5
20
  ## Requisitos
6
21
 
7
- - Python 3.8+
8
- - customtkinter
22
+ - **Python**: 3.11+ (instalado automaticamente por uv)
23
+ - **uv**: https://docs.astral.sh/uv/
9
24
 
10
- ## Instalacion
11
-
12
- ```bash
13
- pip install -r requirements.txt
14
- ```
15
-
16
- ## Ejecutar
25
+ ## Ejecucion
17
26
 
18
27
  ```bash
19
28
  run.bat
20
29
  ```
21
30
 
22
- ## Portable
23
-
24
- Ejecuta `build-portable.bat` - requiere Python instalado en el sistema.
25
-
26
- ## Estructura
31
+ ## Build Windows
27
32
 
28
- ```
29
- python-customtkinter/
30
- ├── src/
31
- │ ├── main.py # App principal
32
- │ └── core/
33
- │ └── skill.json # Configuracion G360
34
- ├── requirements.txt
35
- ├── run.bat # Ejecutar desarrollo
36
- └── build-portable.bat # Version portable
33
+ ```bash
34
+ build.bat
37
35
  ```
38
36
 
39
- ## Caracteristicas
37
+ Genera `dist/G360-App.exe` usando PyInstaller.
40
38
 
41
- - Tema oscuro G360 (#0b1220, #1a2332, #00d084)
42
- - UI moderna con CustomTkinter
43
- - Listo para expansion
39
+ ## Theme
44
40
 
45
- ## License
41
+ Colores definidos en `src/core/skill.json`, cargados via `G360Theme`.
46
42
 
47
- MIT - G360 Ecosystem
43
+ | Token | Color |
44
+ |---|---|
45
+ | `bg` | `#0b1220` |
46
+ | `surface` | `#1a2332` |
47
+ | `accent` | `#00d084` |
48
+ | `text` | `#f0f4f8` |