Primer commit
This commit is contained in:
@@ -0,0 +1,157 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Corrector automático de Baserow (Mesa de control 749 + Verificador 750) para
|
||||
que el workflow n8n [2004] deje de fallar.
|
||||
|
||||
Base / fuente de verdad:
|
||||
- Nombre de match (749.Nombre y 750."SC BUCEFALO") <- `n8n/cuentas_oficiales.csv`
|
||||
- SUCURSAL / TIENDA <- Verificador CSV local (load_verifier_map),
|
||||
solo donde el CSV tenga dato y 750 esté vacío.
|
||||
Lo que no tiene fuente (solo lo conoce Erandi) NO se inventa: se entrega en
|
||||
generated/reports/baserow_pendientes_erandi.json.
|
||||
|
||||
Acciones (idempotentes; la auditoría solo reporta lo que difiere):
|
||||
- PATCH 749.Nombre = oficial (arregla el 1er match)
|
||||
- PATCH 750."SC BUCEFALO" = oficial (arregla el 2do match)
|
||||
- PATCH 750.SUCURSAL/TIENDA desde el CSV (donde el CSV los tenga)
|
||||
- POST fila nueva en 750 para oficiales ausentes
|
||||
Las filas de 750 cuyo location_id NO es oficial NO se tocan.
|
||||
|
||||
Uso:
|
||||
python scripts/fix_baserow_verificador.py # DRY-RUN (no escribe)
|
||||
python scripts/fix_baserow_verificador.py --apply # aplica (backup previo de 749 y 750)
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
if ROOT_DIR not in sys.path:
|
||||
sys.path.insert(0, ROOT_DIR)
|
||||
|
||||
from paths import REPORTS_DIR # noqa: E402
|
||||
from baserow_client import BaserowClient # noqa: E402
|
||||
from audit_baserow_verificador import ( # noqa: E402
|
||||
audit, TABLE_CUENTAS, TABLE_VERIFICADOR,
|
||||
F_749_NOMBRE, F_SC_BUCEFALO, F_SUCURSAL, F_TIENDA, F_ID_LOC,
|
||||
)
|
||||
|
||||
ERANDI_REPORT = os.path.join(REPORTS_DIR, "baserow_pendientes_erandi.json")
|
||||
|
||||
|
||||
def build_plan(res):
|
||||
"""Acciones automatizables (updates 749/750, creates) + lista para Erandi."""
|
||||
updates = [] # {table, row_id, fields, motivo}
|
||||
creates = [] # {table, fields, motivo}
|
||||
erandi = {} # location_id -> {nombre, falta:set}
|
||||
|
||||
def need(loc, nombre, col):
|
||||
erandi.setdefault(loc, {"location_id": loc, "nombre": nombre, "falta": set()})["falta"].add(col)
|
||||
|
||||
for f in res["fix_749"]:
|
||||
updates.append({"table": TABLE_CUENTAS, "row_id": f["row_id"], "fields": {F_749_NOMBRE: f["oficial"]},
|
||||
"motivo": f"749.Nombre {f['actual']!r} -> {f['oficial']!r}"})
|
||||
for m in res["mismatch_750"]:
|
||||
updates.append({"table": TABLE_VERIFICADOR, "row_id": m["row_id"], "fields": {F_SC_BUCEFALO: m["oficial"]},
|
||||
"motivo": f"750.SC_BUCEFALO {m['actual']!r} -> {m['oficial']!r}"})
|
||||
for s in res["sucursal_vacia"]:
|
||||
if s["fuente"] == "csv_tiene":
|
||||
updates.append({"table": TABLE_VERIFICADOR, "row_id": s["row_id"], "fields": {F_SUCURSAL: s["csv_sucursal"]},
|
||||
"motivo": f"SUCURSAL vacío -> {s['csv_sucursal']!r} (CSV)"})
|
||||
else:
|
||||
need(s["location_id"], s["oficial"], "SUCURSAL")
|
||||
for t in res["tienda_vacia"]:
|
||||
if t["fuente"] == "csv_tiene":
|
||||
updates.append({"table": TABLE_VERIFICADOR, "row_id": t["row_id"], "fields": {F_TIENDA: t["csv_tienda"]},
|
||||
"motivo": f"TIENDA vacío -> {t['csv_tienda']!r} (CSV)"})
|
||||
else:
|
||||
need(t["location_id"], t["oficial"], "TIENDA")
|
||||
for a in res["ausente_750"]:
|
||||
fields = {F_ID_LOC: a["location_id"], F_SC_BUCEFALO: a["oficial"]}
|
||||
if a["csv_sucursal"]:
|
||||
fields[F_SUCURSAL] = a["csv_sucursal"]
|
||||
if a["csv_tienda"]:
|
||||
fields[F_TIENDA] = a["csv_tienda"]
|
||||
creates.append({"table": TABLE_VERIFICADOR, "fields": fields, "motivo": f"crear fila para {a['oficial']!r}"})
|
||||
for col, val in [("SUCURSAL", a["csv_sucursal"]), ("TIENDA", a["csv_tienda"])]:
|
||||
if not val:
|
||||
need(a["location_id"], a["oficial"], col)
|
||||
|
||||
erandi_final = [{"location_id": k, "nombre": v["nombre"], "falta": sorted(v["falta"])}
|
||||
for k, v in erandi.items()]
|
||||
return updates, creates, erandi_final
|
||||
|
||||
|
||||
def main():
|
||||
if hasattr(sys.stdout, "reconfigure"):
|
||||
sys.stdout.reconfigure(encoding="utf-8")
|
||||
parser = argparse.ArgumentParser(description="Corrige Baserow 749/750 desde la lista oficial + CSV. Dry-run por defecto.")
|
||||
parser.add_argument("--apply", action="store_true", help="Aplica los cambios en Baserow. Sin esto: dry-run.")
|
||||
args = parser.parse_args()
|
||||
dry_run = not args.apply
|
||||
|
||||
client = BaserowClient.from_credentials()
|
||||
res = audit(client=client)
|
||||
updates, creates, erandi = build_plan(res)
|
||||
u749 = [u for u in updates if u["table"] == TABLE_CUENTAS]
|
||||
u750 = [u for u in updates if u["table"] == TABLE_VERIFICADOR]
|
||||
|
||||
print("=" * 72)
|
||||
print("CORRECTOR BASEROW (749 Mesa de control + 750 Verificador)")
|
||||
print("=" * 72)
|
||||
print(f"Modo: {'DRY-RUN (no escribe)' if dry_run else 'APPLY (escribe en Baserow)'}")
|
||||
print(f"PATCH 749: {len(u749)} | PATCH 750: {len(u750)} | POST nuevas: {len(creates)} | pendientes Erandi: {len(erandi)}")
|
||||
print("\n--- PATCH tabla 749 (Mesa de control) ---")
|
||||
for u in u749:
|
||||
print(f" row {u['row_id']}: {u['motivo']}")
|
||||
print("\n--- PATCH tabla 750 (Verificador) ---")
|
||||
for u in u750:
|
||||
print(f" row {u['row_id']}: {u['motivo']}")
|
||||
print("\n--- POST tabla 750 (crear ausentes) ---")
|
||||
for c in creates:
|
||||
print(f" {c['motivo']}: {c['fields']}")
|
||||
print("\n--- PENDIENTES ERANDI (sin fuente; NO se inventan) ---")
|
||||
for e in erandi:
|
||||
print(f" {e['nombre']!r} falta: {e['falta']} [{e['location_id']}]")
|
||||
|
||||
os.makedirs(REPORTS_DIR, exist_ok=True)
|
||||
with open(ERANDI_REPORT, "w", encoding="utf-8") as fh:
|
||||
json.dump(erandi, fh, ensure_ascii=False, indent=2)
|
||||
print(f"\nLista para Erandi -> {ERANDI_REPORT}")
|
||||
|
||||
if dry_run:
|
||||
print("\nDry-run terminado. Revisa el plan y vuelve a correr con --apply para aplicar.")
|
||||
return
|
||||
if not updates and not creates:
|
||||
print("\nNada que aplicar (Baserow ya está alineado).")
|
||||
return
|
||||
|
||||
b749 = client.backup_table(TABLE_CUENTAS, label="mesa_control_fix")
|
||||
b750 = client.backup_table(TABLE_VERIFICADOR, label="verificador_fix")
|
||||
print(f"\nBackups -> {b749}\n {b750}")
|
||||
ok = err = 0
|
||||
for u in updates:
|
||||
try:
|
||||
client.update_row(u["table"], u["row_id"], u["fields"], dry_run=False)
|
||||
ok += 1
|
||||
print(f" OK PATCH t{u['table']} row {u['row_id']}: {u['motivo']}")
|
||||
except Exception as exc:
|
||||
err += 1
|
||||
print(f" ERROR PATCH t{u['table']} row {u['row_id']}: {exc}")
|
||||
for c in creates:
|
||||
try:
|
||||
client.create_row(c["table"], c["fields"], dry_run=False)
|
||||
ok += 1
|
||||
print(f" OK POST t{c['table']}: {c['motivo']}")
|
||||
except Exception as exc:
|
||||
err += 1
|
||||
print(f" ERROR POST t{c['table']} {c['motivo']}: {exc}")
|
||||
print(f"\nAplicado: {ok} acciones, {err} errores.")
|
||||
if err:
|
||||
raise SystemExit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user