g360-cli 1.7.0 → 1.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -3
- package/package.json +16 -6
- package/py/pyproject.toml +4 -4
- package/py/requirements.txt +4 -0
- package/py/src/g360_core/__init__.py +67 -4
- package/py/src/g360_core/__pycache__/__init__.cpython-312.pyc +0 -0
- package/py/src/g360_core/__pycache__/__init__.cpython-314.pyc +0 -0
- package/py/src/g360_core/__pycache__/batch_processor.cpython-312.pyc +0 -0
- package/py/src/g360_core/__pycache__/batch_processor.cpython-314.pyc +0 -0
- package/py/src/g360_core/__pycache__/commercial_engine.cpython-314.pyc +0 -0
- package/py/src/g360_core/__pycache__/logger.cpython-312.pyc +0 -0
- package/py/src/g360_core/__pycache__/logger.cpython-314.pyc +0 -0
- package/py/src/g360_core/__pycache__/pipeline.cpython-312.pyc +0 -0
- package/py/src/g360_core/__pycache__/pipeline.cpython-314.pyc +0 -0
- package/py/src/g360_core/__pycache__/processor.cpython-312.pyc +0 -0
- package/py/src/g360_core/__pycache__/processor.cpython-314.pyc +0 -0
- package/py/src/g360_core/__pycache__/processor_segmentacion.cpython-312.pyc +0 -0
- package/py/src/g360_core/__pycache__/processor_segmentacion.cpython-314.pyc +0 -0
- package/py/src/g360_core/__pycache__/processor_sku.cpython-312.pyc +0 -0
- package/py/src/g360_core/__pycache__/processor_sku.cpython-314.pyc +0 -0
- package/py/src/g360_core/__pycache__/scanner.cpython-312.pyc +0 -0
- package/py/src/g360_core/__pycache__/scanner.cpython-314.pyc +0 -0
- package/py/src/g360_core/__pycache__/utils.cpython-312.pyc +0 -0
- package/py/src/g360_core/__pycache__/utils.cpython-314.pyc +0 -0
- package/py/src/g360_core/batch_processor.py +120 -0
- package/py/src/g360_core/commercial_engine.py +305 -0
- package/py/src/g360_core/logger.py +40 -0
- package/py/src/g360_core/pipeline.py +578 -0
- package/py/src/g360_core/processor.py +634 -0
- package/py/src/g360_core/processor_segmentacion.py +859 -0
- package/py/src/g360_core/processor_sku.py +427 -0
- package/py/src/g360_core/scanner.py +218 -0
- package/py/src/g360_core/utils.py +435 -0
- package/src/assets/templates/python-flet/src/test_ingestion.py +1 -1
- package/src/cli.js +25 -2
- package/src/commands/ingest.js +193 -0
- package/src/commands/scan.js +102 -0
- package/src/commands/validate.js +126 -0
- package/src/lib/python_runner.js +89 -0
- package/py/src/g360_core/flet/__init__.py +0 -3
- package/py/src/g360_core/flet/ingestion_panel.py +0 -218
- package/py/src/g360_core/ingestion.py +0 -480
package/README.md
CHANGED
|
@@ -55,6 +55,8 @@ CLI tool para el ecosistema G360 que permite inicializar proyectos con estructur
|
|
|
55
55
|
- **Inicialización rápida** - Crea proyectos G360 con estructura estándar
|
|
56
56
|
- **Gestión de assets** - Trae componentes, skills y plantillas embebidas
|
|
57
57
|
- **Ingesta ERP** - Normaliza `.xls/.xlsx` de SAP, StarSoft, Spring con `g360 bring ingestion`
|
|
58
|
+
- **Motor de clasificación comercial** - `commercial_engine` clasifica documentos en VENTA/DEVOLUCION/AJUSTE con subtipos (PRECIO_LINEA, CARGO_FIJO, SIN_BASE)
|
|
59
|
+
- **Precio efectivo** - PRECIO_BASE, RECARGO_UNITARIO y PRECIO_EFECTIVO separan precio físico de ajustes financieros FAE
|
|
58
60
|
- **Paquete Python** - `g360-core` en PyPI para pipelines de datos independientes
|
|
59
61
|
- **Auditoría** - Verifica compliance de proyectos G360
|
|
60
62
|
- **Limpieza** - Elimina assets embebidos antes de deployment
|
|
@@ -309,6 +311,35 @@ El paquete pip acompañante `g360-core` se publica en PyPI:
|
|
|
309
311
|
pip install g360-core
|
|
310
312
|
```
|
|
311
313
|
|
|
314
|
+
### `g360-core` — Módulos principales
|
|
315
|
+
|
|
316
|
+
#### `commercial_engine.py`
|
|
317
|
+
|
|
318
|
+
Motor de lógica de negocio para clasificación documental. Única fuente de verdad para reglas comerciales.
|
|
319
|
+
|
|
320
|
+
| Función | Propósito |
|
|
321
|
+
|---------|-----------|
|
|
322
|
+
| `classify_base()` | Clasificación primaria: VENTA, DEVOLUCION, AJUSTE |
|
|
323
|
+
| `build_invoice_index()` | Índice de facturas para cruce de referencias |
|
|
324
|
+
| `resolve_document_relationships()` | Asigna SUBTIPO_AJUSTE (PRECIO_LINEA, CARGO_FIJO, SIN_BASE) |
|
|
325
|
+
| `calculate_prices()` | PRECIO_BASE, RECARGO_UNITARIO, PRECIO_EFECTIVO |
|
|
326
|
+
| `parse_referencia()` | Descompone REFERENCIA "F01/204-56287" en tipo/serie/número |
|
|
327
|
+
|
|
328
|
+
**Clasificación de documentos:**
|
|
329
|
+
```
|
|
330
|
+
TPO_DOC CANTIDAD CANTIDAD_FAE → CATEGORIA_OP SUBTIPO_AJUSTE
|
|
331
|
+
F01/BDI ≠0 = VENTA —
|
|
332
|
+
NCR ≠0 = DEVOLUCION —
|
|
333
|
+
NCR 0 ≠0 AJUSTE PRECIO_LINEA / SIN_BASE
|
|
334
|
+
NDB 0 ≠0 AJUSTE CARGO_FIJO / SIN_BASE
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
#### `batch_processor.py`
|
|
338
|
+
|
|
339
|
+
| Función | Propósito |
|
|
340
|
+
|---------|-----------|
|
|
341
|
+
| `read_erp_file()` | Punto único de lectura: .xls (xlrd), .xlsx (openpyxl), .csv. `dtype=str` preserva ceros a la izquierda |
|
|
342
|
+
|
|
312
343
|
---
|
|
313
344
|
|
|
314
345
|
### `g360 list`
|
|
@@ -588,12 +619,15 @@ mi-app/
|
|
|
588
619
|
└── skill.json
|
|
589
620
|
```
|
|
590
621
|
|
|
591
|
-
**Normalización de datos:** La ingesta aplica
|
|
622
|
+
**Normalización de datos:** La ingesta aplica transformaciones automáticas:
|
|
592
623
|
- Parseo de referencias (`F01/201-243065` → tipo, serie, periodo, número)
|
|
593
624
|
- Separación de sucursales (nombre + dirección)
|
|
594
625
|
- Clasificación de documentos (RUC 11 dígitos / DNI 8 dígitos)
|
|
595
626
|
- Normalización monetaria con auto-detección de formato SAP/Spring
|
|
596
627
|
- Cantidad + Cantidad FAE → cantidad_total + tipo_transaccion
|
|
628
|
+
- **Clasificación comercial**: VENTA / DEVOLUCION / AJUSTE con subtipos PRECIO_LINEA, CARGO_FIJO, SIN_BASE
|
|
629
|
+
- **Precio efectivo**: PRECIO_BASE (físico) y RECARGO_UNITARIO (financiero) separados
|
|
630
|
+
- Cruce de NC/NDB contra facturas referenciadas para determinar ajustes de precio por línea
|
|
597
631
|
- Purga de filas total/general/acumulado
|
|
598
632
|
|
|
599
633
|
### python-flet-migrate
|
|
@@ -806,7 +840,7 @@ g360-cli/
|
|
|
806
840
|
│ └── config/ # Configuraciones
|
|
807
841
|
├── py/ # Paquete Python publicable en PyPI
|
|
808
842
|
│ ├── pyproject.toml # g360-core
|
|
809
|
-
│ └── src/g360_core/ #
|
|
843
|
+
│ └── src/g360_core/ # commercial_engine.py, pipeline.py, processor.py, batch_processor.py, utils.py
|
|
810
844
|
├── package.json
|
|
811
845
|
├── README.md
|
|
812
846
|
└── LICENSE
|
|
@@ -834,7 +868,7 @@ npm run test:ui # UI interactiva
|
|
|
834
868
|
npm run test:coverage
|
|
835
869
|
```
|
|
836
870
|
|
|
837
|
-
**Cobertura actual (v1.
|
|
871
|
+
**Cobertura actual (v1.9.0):**
|
|
838
872
|
- `commands/`: init, bring, list, audit, set-skill
|
|
839
873
|
- `lib/`: manifest, validator, asset-validator
|
|
840
874
|
- **51 passing / 1 timeout** (init.test.js requiere import pesado de inquirer)
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "g360-cli",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "CLI tool for bootstrapping G360 projects with standardized structure, assets, and
|
|
3
|
+
"version": "1.9.0",
|
|
4
|
+
"description": "CLI tool for bootstrapping G360 projects with standardized structure, assets, identity, and ERP data processing",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/cli.js",
|
|
7
7
|
"bin": {
|
|
@@ -14,19 +14,25 @@
|
|
|
14
14
|
"test:ui": "vitest --ui",
|
|
15
15
|
"test:coverage": "vitest --coverage",
|
|
16
16
|
"build": "npm run build:portable",
|
|
17
|
-
"build:portable": "pkg . --targets node18-win-x64 --output dist/g360.exe"
|
|
17
|
+
"build:portable": "pkg . --targets node18-win-x64 --output dist/g360.exe",
|
|
18
|
+
"postinstall": "echo '✅ g360-cli instalado. Asegúrate de tener Python 3.11+ y pip instalados.'"
|
|
18
19
|
},
|
|
19
20
|
"pkg": {
|
|
20
21
|
"assets": [
|
|
21
|
-
"src/assets/**/*"
|
|
22
|
+
"src/assets/**/*",
|
|
23
|
+
"py/**/*"
|
|
22
24
|
],
|
|
23
|
-
"outputPath": "dist"
|
|
25
|
+
"outputPath": "dist",
|
|
26
|
+
"scripts": [],
|
|
27
|
+
"targets": "node18-win-x64"
|
|
24
28
|
},
|
|
25
29
|
"keywords": [
|
|
26
30
|
"g360",
|
|
27
31
|
"cli",
|
|
28
32
|
"scaffolding",
|
|
29
|
-
"generator"
|
|
33
|
+
"generator",
|
|
34
|
+
"erp",
|
|
35
|
+
"data-processing"
|
|
30
36
|
],
|
|
31
37
|
"author": "Carlos Cusi",
|
|
32
38
|
"license": "MIT",
|
|
@@ -46,5 +52,9 @@
|
|
|
46
52
|
},
|
|
47
53
|
"engines": {
|
|
48
54
|
"node": ">=18.0.0"
|
|
55
|
+
},
|
|
56
|
+
"repository": {
|
|
57
|
+
"type": "git",
|
|
58
|
+
"url": "https://github.com/ccusi/g360-cli.git"
|
|
49
59
|
}
|
|
50
60
|
}
|
package/py/pyproject.toml
CHANGED
|
@@ -18,10 +18,10 @@ classifiers = [
|
|
|
18
18
|
]
|
|
19
19
|
|
|
20
20
|
dependencies = [
|
|
21
|
-
"pandas>=2.
|
|
22
|
-
"numpy>=1.
|
|
23
|
-
"openpyxl>=3.1.
|
|
24
|
-
"xlrd>=2.0.
|
|
21
|
+
"pandas>=2.2.3",
|
|
22
|
+
"numpy>=1.26.0",
|
|
23
|
+
"openpyxl>=3.1.5",
|
|
24
|
+
"xlrd>=2.0.1",
|
|
25
25
|
]
|
|
26
26
|
|
|
27
27
|
[project.optional-dependencies]
|
|
@@ -1,7 +1,70 @@
|
|
|
1
|
-
|
|
1
|
+
"""
|
|
2
|
+
g360_core - Módulo principal de procesamiento ERP para g360-cli.
|
|
3
|
+
|
|
4
|
+
Proporciona:
|
|
5
|
+
- Estabilización de datos crudos de Excel/CSV
|
|
6
|
+
- Pipeline de ingesta con normalización
|
|
7
|
+
- Scanner de directorios
|
|
8
|
+
- Protección de ceros a la izquierda en IDs
|
|
9
|
+
- Batch processing
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from .commercial_engine import (classify_base, parse_referencia, resolve_document_relationships, calculate_prices, CAT_VENTA, CAT_DEVOLUCION, CAT_AJUSTE, SUBTIPO_PRECIO_LINEA, SUBTIPO_PRECIO_PARCIAL, SUBTIPO_CARGO_FIJO, SUBTIPO_SIN_BASE)
|
|
13
|
+
from .pipeline import ingest_to_master, batch_ingest
|
|
14
|
+
from .scanner import (
|
|
15
|
+
ERPScanner,
|
|
16
|
+
ERPFileInfo,
|
|
17
|
+
find_erp_files_in_dir,
|
|
18
|
+
process_single_file,
|
|
19
|
+
batch_process_files,
|
|
20
|
+
merge_processed_data,
|
|
21
|
+
)
|
|
22
|
+
from .processor import InsightProcessor
|
|
23
|
+
from .utils import (
|
|
24
|
+
clean_erp_headers,
|
|
25
|
+
normalize_ids,
|
|
26
|
+
build_doc_completo,
|
|
27
|
+
build_entity_labels,
|
|
28
|
+
validate_columns,
|
|
29
|
+
parse_excel_date,
|
|
30
|
+
ID_COLS_PRESERVE_ZEROS,
|
|
31
|
+
NC_PREFIXES,
|
|
32
|
+
)
|
|
33
|
+
from .batch_processor import read_erp_file
|
|
2
34
|
|
|
3
35
|
__all__ = [
|
|
4
|
-
|
|
5
|
-
"
|
|
6
|
-
"
|
|
36
|
+
# Commercial Engine
|
|
37
|
+
"classify_base",
|
|
38
|
+
"parse_referencia",
|
|
39
|
+
"resolve_document_relationships",
|
|
40
|
+
"calculate_prices",
|
|
41
|
+
"CAT_VENTA",
|
|
42
|
+
"CAT_DEVOLUCION",
|
|
43
|
+
"CAT_AJUSTE",
|
|
44
|
+
"SUBTIPO_PRECIO_LINEA",
|
|
45
|
+
"SUBTIPO_PRECIO_PARCIAL",
|
|
46
|
+
"SUBTIPO_CARGO_FIJO",
|
|
47
|
+
"SUBTIPO_SIN_BASE",
|
|
48
|
+
# Ingestion
|
|
49
|
+
"ingest_to_master",
|
|
50
|
+
"batch_ingest",
|
|
51
|
+
# Scanner
|
|
52
|
+
"ERPScanner",
|
|
53
|
+
"ERPFileInfo",
|
|
54
|
+
"find_erp_files_in_dir",
|
|
55
|
+
"process_single_file",
|
|
56
|
+
"batch_process_files",
|
|
57
|
+
"merge_processed_data",
|
|
58
|
+
# Processor
|
|
59
|
+
"InsightProcessor",
|
|
60
|
+
# Utils
|
|
61
|
+
"clean_erp_headers",
|
|
62
|
+
"normalize_ids",
|
|
63
|
+
"build_doc_completo",
|
|
64
|
+
"build_entity_labels",
|
|
65
|
+
"validate_columns",
|
|
66
|
+
"parse_excel_date",
|
|
67
|
+
"ID_COLS_PRESERVE_ZEROS",
|
|
68
|
+
"NC_PREFIXES",
|
|
69
|
+
"read_erp_file",
|
|
7
70
|
]
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Procesador por lotes para archivos ERP grandes.
|
|
3
|
+
|
|
4
|
+
Lee archivos grandes y los procesa en un solo paso usando InsightProcessor.
|
|
5
|
+
El chunking original causaba procesamiento duplicado; ahora se procesa
|
|
6
|
+
todo el DataFrame de una vez para garantizar consistencia.
|
|
7
|
+
"""
|
|
8
|
+
import pandas as pd
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Optional, Callable
|
|
11
|
+
import gc
|
|
12
|
+
from .processor import InsightProcessor
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class BatchProcessor:
|
|
16
|
+
"""Procesa archivos ERP grandes con feedback de progreso."""
|
|
17
|
+
|
|
18
|
+
def __init__(self, progress_callback: Optional[Callable] = None):
|
|
19
|
+
self.progress_callback = progress_callback
|
|
20
|
+
|
|
21
|
+
def process_large_file(self, df: pd.DataFrame, filename: str) -> InsightProcessor:
|
|
22
|
+
"""
|
|
23
|
+
Procesa un DataFrame completo en un solo paso.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
df: DataFrame con datos crudos
|
|
27
|
+
filename: Nombre del archivo para logs
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
InsightProcessor con todos los datos procesados
|
|
31
|
+
"""
|
|
32
|
+
if self.progress_callback:
|
|
33
|
+
self.progress_callback(0.1, f"Procesando {len(df):,} registros...")
|
|
34
|
+
|
|
35
|
+
processor = InsightProcessor(df)
|
|
36
|
+
gc.collect()
|
|
37
|
+
|
|
38
|
+
if self.progress_callback:
|
|
39
|
+
self.progress_callback(1.0, "Procesamiento completado")
|
|
40
|
+
|
|
41
|
+
return processor
|
|
42
|
+
|
|
43
|
+
def read_with_progress(self, path: str, file_ext: str,
|
|
44
|
+
on_progress: Callable[[float, str], None]) -> pd.DataFrame:
|
|
45
|
+
"""
|
|
46
|
+
Lee archivo con indicador de progreso.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
path: Ruta del archivo
|
|
50
|
+
file_ext: Extension del archivo (.csv, .xls, .xlsx)
|
|
51
|
+
on_progress: Callback de progreso (0-1, mensaje)
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
DataFrame leido
|
|
55
|
+
"""
|
|
56
|
+
on_progress(0.05, "Leyendo archivo...")
|
|
57
|
+
df = read_erp_file(path, file_ext)
|
|
58
|
+
on_progress(0.2, f"Archivo leido: {len(df):,} filas")
|
|
59
|
+
return df
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def read_erp_file(path: str, file_ext: str = None) -> pd.DataFrame:
|
|
63
|
+
"""
|
|
64
|
+
Funcion unificada de lectura de archivos ERP.
|
|
65
|
+
|
|
66
|
+
Es el unico punto de entrada para leer archivos .xls, .xlsx y .csv
|
|
67
|
+
en toda la aplicacion. Usa engines explicitos para evitar problemas
|
|
68
|
+
de compatibilidad con pandas/xlrd/openpyxl.
|
|
69
|
+
|
|
70
|
+
PROTECCIÓN DE CEROS A LA IZQUIERDA:
|
|
71
|
+
Las columnas críticas de IDs se leen obligatoriamente como strings (dtype=str)
|
|
72
|
+
para preservar ceros iniciales (ej: "01240" no se convierte a 1240).
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
path: Ruta absoluta al archivo.
|
|
76
|
+
file_ext: Extension (incluye el punto). Si es None se infiere.
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
DataFrame con columnas críticas como strings para preservar ceros.
|
|
80
|
+
|
|
81
|
+
Raises:
|
|
82
|
+
ValueError: Si la extension no es soportada o hay error de lectura.
|
|
83
|
+
"""
|
|
84
|
+
if file_ext is None:
|
|
85
|
+
file_ext = Path(path).suffix.lower()
|
|
86
|
+
else:
|
|
87
|
+
file_ext = file_ext.lower()
|
|
88
|
+
|
|
89
|
+
# Columnas críticas que deben preservar ceros a la izquierda
|
|
90
|
+
critical_cols = [
|
|
91
|
+
"ID_ARTICULO", "ID_CLIENTE", "ID_VENDEDOR", "ID_LINEA",
|
|
92
|
+
"ID_GRUPO", "ID_TIPO", "ID_FAMILIA", "COD_SUCURSAL",
|
|
93
|
+
"NRO_DOC", "SERIE_DOC", "DOC_CLIENTE"
|
|
94
|
+
]
|
|
95
|
+
|
|
96
|
+
if file_ext == '.csv':
|
|
97
|
+
# Para CSV, leer todo como string inicialmente
|
|
98
|
+
df = pd.read_csv(
|
|
99
|
+
path, dtype=str, encoding='utf-8-sig',
|
|
100
|
+
sep=None, engine='python', low_memory=False,
|
|
101
|
+
)
|
|
102
|
+
elif file_ext in ('.xls', '.xlsx'):
|
|
103
|
+
engine = "xlrd" if file_ext == ".xls" else "openpyxl"
|
|
104
|
+
try:
|
|
105
|
+
# Leer Excel con dtype=str para forzar texto en TODAS las columnas
|
|
106
|
+
# Esto previene que pandas convierta "01240" -> 1240 automáticamente
|
|
107
|
+
df = pd.read_excel(path, dtype=str, engine=engine)
|
|
108
|
+
except Exception as ex:
|
|
109
|
+
raise ValueError(
|
|
110
|
+
f"Error leyendo archivo Excel ({file_ext}) con motor '{engine}': {ex}"
|
|
111
|
+
)
|
|
112
|
+
else:
|
|
113
|
+
raise ValueError(f"Extension '{file_ext}' no soportada. Use .xls, .xlsx, .csv")
|
|
114
|
+
|
|
115
|
+
# Limpieza post-lectura: eliminar espacios en blanco de columnas críticas
|
|
116
|
+
for col in critical_cols:
|
|
117
|
+
if col in df.columns:
|
|
118
|
+
df[col] = df[col].astype(str).str.strip()
|
|
119
|
+
|
|
120
|
+
return df
|