#!/usr/bin/env python3 """Step 2: update Fuente de Prospecto from Canal de Origen.""" import argparse from tag_canal_origen_workflow import ( MAIN_LOCATION_ID, build_contact_match_index, contact_display_name, find_exact_main_contact, get_all_contacts, get_custom_field_value, get_main_account, get_schemas, load_locations, safe_update_contact_field, script_audit, select_locations, ) from canal_origen_resolver import classify_source SUCURSAL_VALUE = "SUCURSAL" LEAD_DIGITAL_VALUE = "LEAD DIGITAL" def process_location(account, dry_run=False, skip_correct=True, run_id=None, sync_main=False): location_id = account["location_id"] token = account["token"] name = account["nombre"] contact_schema = get_schemas(location_id, token, "contact")["contact"] canal_field_id = contact_schema.get("Canal de Origen") fuente_field_id = contact_schema.get("Fuente de Prospecto") main_account = get_main_account() if sync_main and location_id != MAIN_LOCATION_ID else None main_contact_schema = {} main_index = None if main_account: main_contact_schema = get_schemas(main_account["location_id"], main_account["token"], "contact")["contact"] main_contacts = get_all_contacts(main_account["location_id"], main_account["token"]) main_index = build_contact_match_index(main_contacts) if not canal_field_id or not fuente_field_id: print( f"\n{name} - missing required fields " f"(Canal: {bool(canal_field_id)}, Fuente: {bool(fuente_field_id)})" ) return 0 updated = 0 skipped = 0 contacts = get_all_contacts(location_id, token) inconsistencies = 0 for contact in contacts: if not script_audit.wait_if_paused_or_stopped(run_id): print("\nDetención segura solicitada. Saliendo antes del siguiente contacto.") break canal_value = get_custom_field_value(contact, canal_field_id) current_fuente = get_custom_field_value(contact, fuente_field_id) source_value = contact.get("source") source_tag = classify_source(source_value) if (canal_value == SUCURSAL_VALUE) and source_tag in ("facebook-ads", "formulario"): inconsistencies += 1 print( f" INCONGRUENTE {contact_display_name(contact)} | Canal=SUCURSAL pero " f"source='{source_value}' sugiere '{source_tag}' (se respeta Canal actual; " "revisar Step 1)" ) target_fuente = SUCURSAL_VALUE if canal_value == SUCURSAL_VALUE else LEAD_DIGITAL_VALUE branch_changed = False if skip_correct and current_fuente == target_fuente: skipped += 1 else: branch_changed = safe_update_contact_field( run_id, location_id, contact, fuente_field_id, "Fuente de Prospecto", target_fuente, token, dry_run, ) if branch_changed: updated += 1 print( f" OK {contact_display_name(contact)} | " f"Canal: {canal_value or '(vacio)'} -> Fuente: {target_fuente}" ) if main_account and main_index and main_contact_schema.get("Fuente de Prospecto"): main_contact, match_type = find_exact_main_contact(contact, main_index) if main_contact: main_canal_id = main_contact_schema.get("Canal de Origen") main_canal_value = get_custom_field_value(main_contact, main_canal_id) if main_canal_id else canal_value main_target_fuente = SUCURSAL_VALUE if main_canal_value == SUCURSAL_VALUE else LEAD_DIGITAL_VALUE if safe_update_contact_field( run_id, main_account["location_id"], main_contact, main_contact_schema["Fuente de Prospecto"], "Fuente de Prospecto", main_target_fuente, main_account["token"], dry_run, ): print(f" OK marca principal ({match_type}) {contact_display_name(main_contact)} -> Fuente: {main_target_fuente}") print( f"\n{name}: {updated} contactos actualizados, {skipped} ya correctos, " f"{inconsistencies} incongruencias canal/source" ) return updated def main(): parser = argparse.ArgumentParser(description="GHL Step 2: Canal de Origen -> Fuente de Prospecto") parser.add_argument("--location", help="Specific location ID to process") parser.add_argument("--all", action="store_true", help="Process all branch locations from the CSV") parser.add_argument("--include-main", action="store_true", help="Include the main brand account when using --all") parser.add_argument("--dry-run", action="store_true", help="Preview changes without writing to GHL") parser.add_argument("--no-skip", action="store_true", help="Update even if Fuente de Prospecto already matches") parser.add_argument("--sync-main", action="store_true", help="Also sync exact matching contact in the main brand account") parser.add_argument("--run-id", help="Audit run ID supplied by the dashboard") args = parser.parse_args() # Keep the imported helpers explicit so packaging/static checks see they are intentional. _ = load_locations print("\n" + "=" * 60) print("GHL WORKFLOW STEP 2 - FUENTE DE PROSPECTO") print("=" * 60) if args.dry_run: print("DRY RUN - no changes will be made\n") total = 0 for account in select_locations(args): try: total += process_location(account, args.dry_run, not args.no_skip, args.run_id, args.sync_main) except Exception as exc: print(f"\nERROR {account['nombre']}: {exc}") print(f"\n{'=' * 60}") print(f"TOTAL: {total} contactos actualizados") if __name__ == "__main__": main()