g360-cli 1.10.1 → 1.11.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 +6 -2
- package/package.json +1 -1
- package/src/assets/ingestion/core/__pycache__/ingestion.cpython-312.pyc +0 -0
- package/src/assets/ingestion/core/ingestion.py +47 -0
- package/src/assets/templates/python-flet/src/core/ingestion.py +66 -0
- package/src/assets/templates/python-flet/src/test_ingestion.py +14 -0
package/README.md
CHANGED
|
@@ -70,7 +70,7 @@ CLI tool para el ecosistema G360 que permite inicializar proyectos con estructur
|
|
|
70
70
|
|
|
71
71
|
## Versión
|
|
72
72
|
|
|
73
|
-
**Current: v1.
|
|
73
|
+
**Current: v1.11.0** — [Ver en npm](https://www.npmjs.com/package/g360-cli)
|
|
74
74
|
|
|
75
75
|
---
|
|
76
76
|
|
|
@@ -91,7 +91,7 @@ npm install -g g360-cli
|
|
|
91
91
|
|
|
92
92
|
```bash
|
|
93
93
|
g360 --version
|
|
94
|
-
# → 1.
|
|
94
|
+
# → 1.11.0
|
|
95
95
|
|
|
96
96
|
g360 health
|
|
97
97
|
```
|
|
@@ -646,6 +646,10 @@ mi-app/
|
|
|
646
646
|
- **Precio efectivo**: PRECIO_BASE (físico) y RECARGO_UNITARIO (financiero) separados
|
|
647
647
|
- Cruce de NC/NDB contra facturas referenciadas para determinar ajustes de precio por línea
|
|
648
648
|
- Purga de filas total/general/acumulado
|
|
649
|
+
- **Columnas derivadas para UI**: `cliente_label`, `vendedor_label`, `articulo_label`, `linea_label`, etc. (ID - NOMBRE)
|
|
650
|
+
- **Cliente completo**: `cliente_full_label` = ID_CLIENTE - DOC_CLIENTE_CLEAN - NOM_CLIENTE
|
|
651
|
+
- **Código de factura**: `doc_completo` = TPO_DOC + SERIE_DOC + NRO_DOC (ej: "F204-56287")
|
|
652
|
+
- **Precio unitario**: `precio_base` = SOLES / CANTIDAD
|
|
649
653
|
|
|
650
654
|
### python-flet-migrate
|
|
651
655
|
|
package/package.json
CHANGED
|
Binary file
|
|
@@ -25,6 +25,17 @@ _KEYWORDS_CANTIDAD = [
|
|
|
25
25
|
]
|
|
26
26
|
_KEYWORDS_FECHA = ["fecha", "fec_", "venc"]
|
|
27
27
|
|
|
28
|
+
_ENTITY_LABELS = {
|
|
29
|
+
("id_cliente", "nom_cliente"): "cliente_label",
|
|
30
|
+
("id_vendedor", "nom_vendedor"): "vendedor_label",
|
|
31
|
+
("id_articulo", "nom_articulo"): "articulo_label",
|
|
32
|
+
("id_linea", "nom_linea"): "linea_label",
|
|
33
|
+
("id_grupo", "nom_grupo"): "grupo_label",
|
|
34
|
+
("id_tipo", "nom_tipo"): "tipo_label",
|
|
35
|
+
("id_familia", "nom_familia"): "familia_label",
|
|
36
|
+
("cod_sucursal", "nom_sucursal"): "sucursal_label",
|
|
37
|
+
}
|
|
38
|
+
|
|
28
39
|
_MAPA_TPO_DOC = {
|
|
29
40
|
"NCR": "NOTA DE CREDITO",
|
|
30
41
|
"NDB": "NOTA DE DEBITO",
|
|
@@ -247,6 +258,26 @@ def _clasificar_transaccion(row: dict) -> str:
|
|
|
247
258
|
return "indefinido"
|
|
248
259
|
|
|
249
260
|
|
|
261
|
+
def _build_entity_labels(df: pd.DataFrame) -> pd.DataFrame:
|
|
262
|
+
for (id_col, name_col), label_col in _ENTITY_LABELS.items():
|
|
263
|
+
id_exists = id_col in df.columns
|
|
264
|
+
name_exists = name_col in df.columns
|
|
265
|
+
|
|
266
|
+
if id_exists and name_exists:
|
|
267
|
+
id_vals = df[id_col].fillna("").astype(str).str.strip()
|
|
268
|
+
name_vals = df[name_col].fillna("").astype(str).str.strip()
|
|
269
|
+
df[label_col] = np.where(
|
|
270
|
+
(id_vals != "") & (name_vals != ""),
|
|
271
|
+
id_vals + " - " + name_vals,
|
|
272
|
+
np.where(id_vals != "", id_vals, name_vals)
|
|
273
|
+
)
|
|
274
|
+
elif id_exists:
|
|
275
|
+
df[label_col] = df[id_col].astype(str).str.strip()
|
|
276
|
+
elif name_exists:
|
|
277
|
+
df[label_col] = df[name_col].astype(str).str.strip()
|
|
278
|
+
return df
|
|
279
|
+
|
|
280
|
+
|
|
250
281
|
def estabilizar_excel_crudo(ruta_archivo: str | Path) -> tuple[pd.DataFrame, dict]:
|
|
251
282
|
ruta = Path(ruta_archivo)
|
|
252
283
|
if not ruta.exists():
|
|
@@ -359,6 +390,22 @@ def estabilizar_excel_crudo(ruta_archivo: str | Path) -> tuple[pd.DataFrame, dic
|
|
|
359
390
|
f"{col}: normalizado ({ruc_count} RUC, {dni_count} DNI, "
|
|
360
391
|
f"{(~df[tipo_col].isin(['RUC','DNI'])).sum()} otros)"
|
|
361
392
|
)
|
|
393
|
+
|
|
394
|
+
# ── 9b. CLIENTE_FULL_LABEL = ID + DOC_CLIENTE_CLEAN + NOMBRE ──
|
|
395
|
+
if "id_cliente" in df.columns and "doc_cliente_clean" in df.columns:
|
|
396
|
+
id_vals = df["id_cliente"].fillna("").astype(str).str.strip()
|
|
397
|
+
doc_vals = df["doc_cliente_clean"].fillna("").astype(str).str.strip()
|
|
398
|
+
name_vals = df["nom_cliente"].fillna("").astype(str).str.strip() if "nom_cliente" in df.columns else None
|
|
399
|
+
|
|
400
|
+
if name_vals is not None:
|
|
401
|
+
df["cliente_full_label"] = np.where(
|
|
402
|
+
(id_vals != "") & (doc_vals != "") & (name_vals != ""),
|
|
403
|
+
id_vals + " - " + doc_vals + " - " + name_vals,
|
|
404
|
+
np.where((id_vals != "") & (doc_vals != ""), id_vals + " - " + doc_vals, id_vals)
|
|
405
|
+
)
|
|
406
|
+
else:
|
|
407
|
+
df["cliente_full_label"] = id_vals + " - " + doc_vals
|
|
408
|
+
transformaciones.append("cliente_full_label: ID + DOC_CLIENTE_CLEAN + NOM_CLIENTE")
|
|
362
409
|
|
|
363
410
|
# ── 10. Columnas TEXTO ──
|
|
364
411
|
for col in df.columns:
|
|
@@ -25,6 +25,17 @@ _KEYWORDS_CANTIDAD = [
|
|
|
25
25
|
]
|
|
26
26
|
_KEYWORDS_FECHA = ["fecha", "fec_", "venc"]
|
|
27
27
|
|
|
28
|
+
_ENTITY_LABELS = {
|
|
29
|
+
("id_cliente", "nom_cliente"): "cliente_label",
|
|
30
|
+
("id_vendedor", "nom_vendedor"): "vendedor_label",
|
|
31
|
+
("id_articulo", "nom_articulo"): "articulo_label",
|
|
32
|
+
("id_linea", "nom_linea"): "linea_label",
|
|
33
|
+
("id_grupo", "nom_grupo"): "grupo_label",
|
|
34
|
+
("id_tipo", "nom_tipo"): "tipo_label",
|
|
35
|
+
("id_familia", "nom_familia"): "familia_label",
|
|
36
|
+
("cod_sucursal", "nom_sucursal"): "sucursal_label",
|
|
37
|
+
}
|
|
38
|
+
|
|
28
39
|
_MAPA_TPO_DOC = {
|
|
29
40
|
"NCR": "NOTA DE CREDITO",
|
|
30
41
|
"NDB": "NOTA DE DEBITO",
|
|
@@ -247,6 +258,26 @@ def _clasificar_transaccion(row: dict) -> str:
|
|
|
247
258
|
return "indefinido"
|
|
248
259
|
|
|
249
260
|
|
|
261
|
+
def _build_entity_labels(df: pd.DataFrame) -> pd.DataFrame:
|
|
262
|
+
for (id_col, name_col), label_col in _ENTITY_LABELS.items():
|
|
263
|
+
id_exists = id_col in df.columns
|
|
264
|
+
name_exists = name_col in df.columns
|
|
265
|
+
|
|
266
|
+
if id_exists and name_exists:
|
|
267
|
+
id_vals = df[id_col].fillna("").astype(str).str.strip()
|
|
268
|
+
name_vals = df[name_col].fillna("").astype(str).str.strip()
|
|
269
|
+
df[label_col] = np.where(
|
|
270
|
+
(id_vals != "") & (name_vals != ""),
|
|
271
|
+
id_vals + " - " + name_vals,
|
|
272
|
+
np.where(id_vals != "", id_vals, name_vals)
|
|
273
|
+
)
|
|
274
|
+
elif id_exists:
|
|
275
|
+
df[label_col] = df[id_col].astype(str).str.strip()
|
|
276
|
+
elif name_exists:
|
|
277
|
+
df[label_col] = df[name_col].astype(str).str.strip()
|
|
278
|
+
return df
|
|
279
|
+
|
|
280
|
+
|
|
250
281
|
def estabilizar_excel_crudo(ruta_archivo: str | Path) -> tuple[pd.DataFrame, dict]:
|
|
251
282
|
ruta = Path(ruta_archivo)
|
|
252
283
|
if not ruta.exists():
|
|
@@ -359,6 +390,22 @@ def estabilizar_excel_crudo(ruta_archivo: str | Path) -> tuple[pd.DataFrame, dic
|
|
|
359
390
|
f"{col}: normalizado ({ruc_count} RUC, {dni_count} DNI, "
|
|
360
391
|
f"{(~df[tipo_col].isin(['RUC','DNI'])).sum()} otros)"
|
|
361
392
|
)
|
|
393
|
+
|
|
394
|
+
# ── 9b. CLIENTE_FULL_LABEL = ID + DOC_CLIENTE_CLEAN + NOMBRE ──
|
|
395
|
+
if "id_cliente" in df.columns and "doc_cliente_clean" in df.columns:
|
|
396
|
+
id_vals = df["id_cliente"].fillna("").astype(str).str.strip()
|
|
397
|
+
doc_vals = df["doc_cliente_clean"].fillna("").astype(str).str.strip()
|
|
398
|
+
name_vals = df["nom_cliente"].fillna("").astype(str).str.strip() if "nom_cliente" in df.columns else None
|
|
399
|
+
|
|
400
|
+
if name_vals is not None:
|
|
401
|
+
df["cliente_full_label"] = np.where(
|
|
402
|
+
(id_vals != "") & (doc_vals != "") & (name_vals != ""),
|
|
403
|
+
id_vals + " - " + doc_vals + " - " + name_vals,
|
|
404
|
+
np.where((id_vals != "") & (doc_vals != ""), id_vals + " - " + doc_vals, id_vals)
|
|
405
|
+
)
|
|
406
|
+
else:
|
|
407
|
+
df["cliente_full_label"] = id_vals + " - " + doc_vals
|
|
408
|
+
transformaciones.append("cliente_full_label: ID + DOC_CLIENTE_CLEAN + NOM_CLIENTE")
|
|
362
409
|
|
|
363
410
|
# ── 10. Columnas TEXTO ──
|
|
364
411
|
for col in df.columns:
|
|
@@ -386,6 +433,18 @@ def estabilizar_excel_crudo(ruta_archivo: str | Path) -> tuple[pd.DataFrame, dic
|
|
|
386
433
|
df[col] = pd.to_numeric(df[col], errors="coerce").fillna(0.0)
|
|
387
434
|
transformaciones.append(f"{col}: forzado a float64, NaN → 0.0")
|
|
388
435
|
|
|
436
|
+
# ── 12b. PRECIO_BASE (precio unitario por fila) ──
|
|
437
|
+
if "soles" in df.columns and cols_cantidad:
|
|
438
|
+
cant_col = cols_cantidad[0]
|
|
439
|
+
cant = df[cant_col].fillna(0)
|
|
440
|
+
soles = df["soles"].fillna(0)
|
|
441
|
+
df["precio_base"] = np.where(
|
|
442
|
+
cant != 0,
|
|
443
|
+
np.round(soles / cant.abs(), 4),
|
|
444
|
+
np.nan
|
|
445
|
+
)
|
|
446
|
+
transformaciones.append(f"precio_base: SOLES / {cant_col}")
|
|
447
|
+
|
|
389
448
|
# ── 13. cantidad + cantidad_fae → cantidad_total, tipo_transaccion ──
|
|
390
449
|
if "cantidad" in df.columns:
|
|
391
450
|
if "cantidad_fae" in df.columns:
|
|
@@ -397,6 +456,13 @@ def estabilizar_excel_crudo(ruta_archivo: str | Path) -> tuple[pd.DataFrame, dic
|
|
|
397
456
|
t_counts = df["tipo_transaccion"].value_counts().to_dict()
|
|
398
457
|
transformaciones.append(f"tipo_transaccion: {t_counts}")
|
|
399
458
|
|
|
459
|
+
# ── 13b. Construir columnas _LABEL (ID - Nombre) para UI ──
|
|
460
|
+
cols_before_labels = list(df.columns)
|
|
461
|
+
df = _build_entity_labels(df)
|
|
462
|
+
label_cols_added = [c for c in df.columns if c.endswith("_label") and c not in cols_before_labels]
|
|
463
|
+
if label_cols_added:
|
|
464
|
+
transformaciones.append(f"labels: {', '.join(label_cols_added)} generados")
|
|
465
|
+
|
|
400
466
|
# ── 14. Columnas FECHA ──
|
|
401
467
|
for col in df.columns:
|
|
402
468
|
if _coincide_keywords(col, _KEYWORDS_FECHA) and col != "fec_":
|
|
@@ -46,9 +46,12 @@ _SAMPLE_DF = pd.DataFrame({
|
|
|
46
46
|
"ANHO": [2026.0, 2026.0, np.nan],
|
|
47
47
|
"ID_ARTICULO": ["11030", "78456", np.nan],
|
|
48
48
|
"NOM_CLIENTE": [" CLIENTE A ", "CLIENTE B", np.nan],
|
|
49
|
+
"ID_CLIENTE": ["001", "002", np.nan],
|
|
50
|
+
"DOC_CLIENTE": ["20491653745", "12345678", np.nan],
|
|
49
51
|
"SOLES": [1172.10, "S/.2,500.50", np.nan],
|
|
50
52
|
"FECHA_ORIG": ["24/06/2026", "20/06/2026", np.nan],
|
|
51
53
|
"ID_LINEA": ["0111", "0178", np.nan],
|
|
54
|
+
"NOM_LINEA": ["LINEA A", "LINEA B", np.nan],
|
|
52
55
|
})
|
|
53
56
|
|
|
54
57
|
|
|
@@ -104,6 +107,17 @@ class TestEstabilizarExcelCrudo(unittest.TestCase):
|
|
|
104
107
|
with self.assertRaises(FileNotFoundError):
|
|
105
108
|
estabilizar_excel_crudo("no_existe_archivo_falso_999.xls")
|
|
106
109
|
|
|
110
|
+
def test_cliente_full_label_creado(self):
|
|
111
|
+
df, _ = _patched_ingestion(_SAMPLE_DF)
|
|
112
|
+
self.assertIn("cliente_full_label", df.columns)
|
|
113
|
+
self.assertEqual(df["cliente_full_label"].iloc[0], "001 - 20491653745 - CLIENTE A")
|
|
114
|
+
|
|
115
|
+
def test_labels_generados(self):
|
|
116
|
+
df, meta = _patched_ingestion(_SAMPLE_DF)
|
|
117
|
+
self.assertIn("cliente_label", df.columns)
|
|
118
|
+
self.assertIn("linea_label", df.columns)
|
|
119
|
+
self.assertIn("cliente_full_label", df.columns)
|
|
120
|
+
|
|
107
121
|
|
|
108
122
|
class TestColumnasDuplicadas(unittest.TestCase):
|
|
109
123
|
def test_duplicados_se_sufijan(self):
|