#!/usr/bin/env python3
"""Perbaikan harian tslaw.id - 14 Oktober 2026.
PRIORITAS 2: artikel #2944 (7-langkah-hukum-menagih-utang-piutang-...) 764 kata -> 1.000+ kata
dengan isi berguna (dasar hukum, alat bukti, kedaluwarsa, jalur gugatan, batas legal, pencegahan),
2 FAQ baru + skema FAQPage diselaraskan 5 -> 7 item, dan perbaikan integritas HTML (<p><p>).

Tulis lewat REST API JSON (BUKAN XML-RPC). Dry run default, --apply untuk menulis.
"""
import base64, json, re, sys, time, urllib.request, urllib.error

DOM = "tslaw.id"
USER, PASS = "bambangbot", "EFKm EYGx VQCP Y9go ncPr uM2Y"
AUTH = base64.b64encode(f"{USER}:{PASS}".encode()).decode()
UA = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/124 Safari/537.36"}
FOLDER = "/home/mantapman/.hermes/profiles/atun/workspace/tslaw_pemulihan/perbaikan_20261014"
POST_ID = 2944
TGL = "20261014"

BLOK_BARU = """<h2 class="wp-block-heading">Dasar Hukum Penagihan Utang Piutang</h2>
<p>Utang piutang pada dasarnya adalah perjanjian pinjam pakai habis. Pasal 1754 KUHPerdata menyebut pinjam pakai habis sebagai perjanjian yang menentukan pihak pertama menyerahkan sejumlah barang yang dapat habis terpakai kepada pihak kedua, dengan syarat pihak kedua mengembalikan barang sejenis kepada pihak pertama dalam jumlah dan keadaan yang sama. Uang termasuk barang yang habis terpakai, sehingga hubungan kreditor dan debitor tunduk pada pasal tersebut. Pasal 1338 KUHPerdata menegaskan bahwa persetujuan yang dibuat sesuai undang-undang berlaku sebagai undang-undang bagi para pihak yang membuatnya dan wajib dilaksanakan dengan itikad baik.</p>
<p>Saat debitor tidak memenuhi kewajibannya, Pasal 1243 KUHPerdata mengatur bahwa penggantian biaya, kerugian dan bunga mulai diwajibkan bila debitor, walaupun telah dinyatakan lalai, tetap lalai untuk memenuhi perikatan itu. Pasal 1236 KUHPerdata memuat dasar yang sama untuk perikatan menyerahkan barang. Pasal 1246 KUHPerdata membatasi ganti rugi pada dua hal, yaitu kerugian yang benar-benar diderita kreditor dan keuntungan yang sedianya dapat diperolehnya. Karena itu nilai ganti rugi harus dihitung dari dokumen, bukan dikira-kira.</p>
<p>Bila dalam perjanjian sudah disepakati jumlah uang tertentu sebagai ganti kerugian, Pasal 1249 KUHPerdata melarang pemberian jumlah yang lebih atau kurang dari angka tersebut. Untuk utang yang hanya berupa pembayaran sejumlah uang, Pasal 1250 KUHPerdata menyatakan ganti rugi karena keterlambatan hanya terdiri atas bunga yang ditentukan undang-undang, wajib dibayar sejak diminta di muka pengadilan, dan tidak perlu dibuktikan adanya kerugian oleh kreditor. Inilah alasan klausul bunga atau denda keterlambatan di dalam perjanjian sangat menentukan hasil akhir penagihan.</p>
<p>Kreditor juga terlindungi oleh jaminan umum. Pasal 1131 KUHPerdata menyatakan segala barang bergerak dan tidak bergerak milik debitor, baik yang sudah ada maupun yang akan ada, menjadi jaminan untuk perikatan perorangan debitor itu. Pasal 1132 KUHPerdata menambahkan bahwa barang-barang itu menjadi jaminan bersama bagi semua kreditor, sedangkan hasil penjualannya dibagi menurut perbandingan piutang masing-masing kecuali bila di antara para kreditor ada alasan sah untuk didahulukan. Dari dua pasal itu terlihat mengapa jaminan khusus membuat posisi kreditor jauh lebih kuat daripada sekadar berpegang pada jaminan umum.</p>
<h2 class="wp-block-heading">Alat Bukti yang Menentukan Hasil Penagihan</h2>
<p>Perkara utang piutang dimenangkan oleh pihak yang dapat membuktikan dalilnya. Pasal 1866 KUHPerdata menyebut lima alat bukti, yaitu bukti tertulis, bukti saksi, persangkaan, pengakuan, dan sumpah. Dalam sengketa utang, bukti tulisan menempati urutan pertama, sehingga kelengkapan dokumen sejak awal menentukan langkah penagihan berikutnya.</p>
<p>Dokumen yang sebaiknya sudah lengkap sebelum penagihan dimulai: perjanjian pokok atau akta utang, faktur dan surat pesanan, tanda terima pembayaran sebagian, berita acara serah terima, serta riwayat korespondensi. Bila perjanjian aslinya tidak ada, pengakuan utang tertulis dari debitor dapat menutup celah itu karena pengakuan termasuk alat bukti dalam Pasal 1866 KUHPerdata.</p>
<p>Bukti elektronik juga dapat dipakai di pengadilan. Pasal 5 Undang-Undang tentang Informasi dan Transaksi Elektronik menyatakan informasi elektronik, dokumen elektronik, dan hasil cetaknya merupakan alat bukti hukum yang sah. UU 11/2008 tersebut telah diubah dua kali, terakhir oleh UU 1/2024. Pesan singkat, surel, dan mutasi rekening yang utuh karena itu layak diarsipkan sejak awal, bukan dikumpulkan setelah perkara berjalan.</p>
<p>Untuk piutang dagang, alur dokumen sebaiknya tidak terputus dari pesanan sampai penagihan. Konfirmasi saldo yang ditandatangani debitor setiap akhir periode memperkuat posisi kreditor bila penagihan berlanjut ke jalur hukum.</p>
<h2 class="wp-block-heading">Kapan Hak Menagih Utang Kedaluwarsa?</h2>
<p>Hak menagih tidak berlaku selamanya. Pasal 1967 KUHPerdata menyatakan semua tuntutan hukum, baik yang bersifat kebendaan maupun yang bersifat perorangan, hapus karena lewat waktu dengan lewatnya waktu tiga puluh tahun, sedangkan orang yang menunjuk adanya lewat waktu itu tidak perlu menunjukkan suatu alas hak. Bagi piutang dagang, tiga puluh tahun memang jauh lebih panjang daripada siklus usaha biasa, tetapi pasal ini menegaskan bahwa hak tagih punya batas akhir yang harus dijaga.</p>
<p>Sejumlah tuntutan justru kedaluwarsa jauh lebih cepat karena diatur secara khusus. Pasal 1971 KUHPerdata menetapkan jangka waktu yang pendek bagi tuntutan tukang kayu, tukang batu dan tukang lain atas bahan dan upah mereka, serta tuntutan pengusaha toko atas barang yang telah diserahkan. Aturan lewat waktu berlaku terhadap siapa saja kecuali pihak yang dikecualikan undang-undang, dan Pasal 1987 KUHPerdata menegaskan lewat waktu tidak mulai berlaku atau berlangsung terhadap anak yang belum dewasa dan orang yang berada di bawah pengampuan.</p>
<p>Praktiknya sederhana. Catat tanggal jatuh tempo setiap piutang, kirim pengingat tertulis berjenjang, lalu simpan salinan surat beserta bukti pengirimannya. Berkas penagihan yang rapi memudahkan advokat menilai apakah piutang masih dapat dituntut atau sudah mendekati batas waktu.</p>
<h2 class="wp-block-heading">Memilih Jalur Hukum Sesuai Nilai Utang</h2>
<p>Nilai utang menentukan jalur yang paling masuk akal. Untuk perkara cidera janji atau perbuatan melawan hukum dengan nilai gugatan materiil paling banyak Rp500.000.000,00, Peraturan Mahkamah Agung Nomor 4 Tahun 2019 menyediakan jalur gugatan sederhana. Perkara jenis ini diperiksa hakim tunggal dan diselesaikan paling lama 25 hari sejak sidang pertama. Syaratnya, masing-masing pihak tidak boleh lebih dari satu penggugat dan satu tergugat kecuali memiliki kepentingan hukum yang sama, dan sengketa hak atas tanah tidak termasuk perkara yang dapat diajukan lewat jalur ini.</p>
<p>Di atas nilai itu, perkara ditempuh lewat gugatan biasa. Pasal 118 HIR menentukan gugatan dialamatkan kepada pengadilan negeri yang daerah hukumnya meliputi tempat tinggal tergugat atau tempat tergugat sebenarnya berdiam bila tempat tinggalnya tidak diketahui. Salah alamat berkas membuat gugatan mengandung cacat formil dan berisiko dinyatakan tidak dapat diterima, sehingga penentuan pengadilan yang berwenang sebaiknya dilakukan sejak awal.</p>
<p>Untuk tagihan besar dengan lebih dari satu kreditor, tersedia jalur kepailitan. Pasal 2 ayat (1) UU 37/2004 tentang Kepailitan dan Penundaan Kewajiban Pembayaran Utang menyatakan debitor yang mempunyai dua atau lebih kreditor dan tidak membayar lunas sedikitnya satu utang yang telah jatuh waktu dan dapat ditagih dapat dinyatakan pailit, baik atas permohonannya sendiri maupun atas permohonan satu atau lebih kreditornya. Undang-undang yang sama juga mengatur penundaan kewajiban pembayaran utang sebagai jalan perdamaian. Jalur ini punya akibat luas bagi debitor, jadi tidak tepat dipakai sebagai langkah pertama.</p>
<p>Bila kreditor memegang jaminan khusus seperti hak tanggungan atau fidusia, penagihan lazim dimulai dari eksekusi jaminan sebelum beracara di pengadilan. Untuk jaminan perorangan, gugatan tetap diajukan kepada penanggung sesuai isi perjanjian penanggungan yang disepakati.</p>
<h2 class="wp-block-heading">Batas yang Tidak Boleh Dilanggar Saat Menagih</h2>
<p>Tekanan fisik, penyitaan barang secara sepihak, penguncian tempat usaha, atau penyebaran identitas debitor bukan cara penagihan yang dibenarkan hukum. Perbuatan semacam itu dapat dinilai sebagai perbuatan melawan hukum. Pasal 1365 KUHPerdata menyatakan tiap perbuatan yang melanggar hukum dan membawa kerugian kepada orang lain mewajibkan orang yang menimbulkan kerugian itu karena kesalahannya untuk menggantikan kerugian tersebut. Debitur yang dirugikan oleh cara penagihan yang kasar justru dapat mengajukan tuntutan balik kepada kreditor.</p>
<p>Menyebarkan data pribadi debitor untuk memberi tekanan juga berisiko, karena pemrosesan dan penyebaran data pribadi diatur dalam UU 27/2022 tentang Pelindungan Data Pribadi. Penagihan yang disertai ancaman pun dapat masuk ranah pidana. Karena itu penagihan yang profesional selalu bertumpu pada dokumen, tenggat yang jelas, dan surat resmi, bukan pada tekanan di luar jalur hukum.</p>
<h2 class="wp-block-heading">Mencegah Piutang Macet Sebelum Terjadi</h2>
<p>Pencegahan jauh lebih murah daripada gugatan. Empat hal layak masuk perjanjian sejak awal: batas waktu pembayaran yang tegas beserta denda atau bunga keterlambatan, kewajiban menyerahkan jaminan untuk nilai tertentu, klausul pengakuan utang yang diperbarui berkala, dan mekanisme somasi sebelum perkara dibawa ke pengadilan.</p>
<p>Di sisi administrasi, pisahkan daftar piutang menurut umur tagihan dan hentikan penyaluran barang atau jasa begitu satu tagihan melewati tenggat. Kreditor yang memahami posisinya sendiri akan lebih cepat menentukan kapan cukup menagih lewat surat dan kapan perlu melibatkan advokat.</p>"""

FAQ_TAMBAHAN = [
    ("Apakah utang tanpa perjanjian tertulis tetap bisa ditagih?",
     "Bisa, sepanjang kreditor dapat membuktikan utang tersebut. Bukti transfer, faktur, kwitansi, percakapan elektronik, dan keterangan saksi termasuk alat bukti yang diakui Pasal 1866 KUHPerdata. Pengakuan utang tertulis dari debitor membuat posisi kreditor lebih kuat."),
    ("Bolehkah kreditor menyita sendiri barang debitor?",
     "Tidak. Penyitaan harta hanya dapat dilakukan lewat proses pengadilan. Menyita atau menahan barang secara sepihak berisiko dinilai sebagai perbuatan melawan hukum menurut Pasal 1365 KUHPerdata dan dapat memunculkan tuntutan balik."),
]

ISI = BLOK_BARU  # dibaca cek_pola_ai.py


def rest(path, data=None, method=None, timeout=180):
    h = {"Authorization": f"Basic {AUTH}", **UA}
    body = None
    if data is not None:
        body = json.dumps(data).encode()
        h["Content-Type"] = "application/json"
    r = urllib.request.Request(f"https://{DOM}{path}", data=body, headers=h, method=method)
    for i in range(5):
        try:
            with urllib.request.urlopen(r, timeout=timeout) as x:
                return x.status, json.loads(x.read())
        except urllib.error.HTTPError as e:
            return e.code, e.read().decode()[:500]
        except Exception as e:
            print(f"  (ulang {i+1}: {str(e)[:70]})")
            time.sleep(10)
    return 0, "gagal"


def kata(html, dengan_skema=True):
    t = html if dengan_skema else re.sub(r"<script[^>]*>.*?</script>", " ", html, flags=re.S | re.I)
    t = re.sub(r"<(script|style)[^>]*>.*?</\1>", " ", t, flags=re.S | re.I) if dengan_skema else t
    t = re.sub(r"<[^>]+>", " ", t)
    t = t.replace("&nbsp;", " ").replace("&amp;", "&")
    return len(re.sub(r"\s+", " ", t).split())


def norm(s):
    return re.sub(r"\s+", " ", re.sub(r"<[^>]+>", " ", s)).strip()


def bangun(lama):
    """Kembalikan (konten_baru, info) atau raise AssertionError bila ada yang tidak sesuai."""
    info = {}
    anchor_faq = "<h2>Pertanyaan yang Sering Diajukan</h2>"
    assert lama.count(anchor_faq) == 1, f"anchor FAQ tidak unik: {lama.count(anchor_faq)}"

    baru = lama.replace(anchor_faq, BLOK_BARU + "\n" + anchor_faq, 1)

    # skema FAQ lama -> objek JSON (supaya bisa diselaraskan tanpa mengubah teks lama)
    m = re.search(r'<script type="application/ld\+json">(\{.*?\})</script>', baru, re.S)
    assert m and baru.count('<script type="application/ld+json">') == 1, "blok skema FAQPage tidak unik/absen"
    skema = json.loads(m.group(1))
    assert skema.get("@type") == "FAQPage", "skema lama bukan FAQPage"
    tanya_lama = [q["name"] for q in skema["mainEntity"]]
    info["faq_lama"] = tanya_lama

    # belum ada skema untuk pertanyaan baru
    for q, a in FAQ_TAMBAHAN:
        assert q not in tanya_lama, f"pertanyaan sudah ada: {q}"

    # tambahkan blok FAQ baru persis sebelum skrip, urut seperti tampil
    blok_faq = "\n".join([f"<h3>{q}</h3>\n<p>{a}</p>" for q, a in FAQ_TAMBAHAN])
    assert baru.count('<script type="application/ld+json">') == 1
    baru = baru.replace('<script type="application/ld+json">', blok_faq + "\n\n" + '<script type="application/ld+json">', 1)

    skema["mainEntity"] = skema["mainEntity"] + [
        {"@type": "Question", "name": q, "acceptedAnswer": {"@type": "Answer", "text": a}} for q, a in FAQ_TAMBAHAN]
    skrip_baru = '<script type="application/ld+json">' + json.dumps(skema, ensure_ascii=False) + "</script>"
    m_skrip = re.search(r'<script type="application/ld\+json">\{.*?\}</script>', baru, re.S)
    assert m_skrip, "blok skema tidak ditemukan setelah penyisipan FAQ"
    baru = baru[:m_skrip.start()] + skrip_baru + baru[m_skrip.end():]

    # integritas: paragraf bersarang <p><p>...</p></p>
    rusak = baru.count("</p></p>")
    info["p_nested"] = rusak
    baru = baru.replace("</p></p>", "</p>")

    # verifikasi skema: semua pertanyaan benar-benar tampil di isi
    m2 = re.search(r'<script type="application/ld\+json">(\{.*?\})</script>', baru, re.S)
    skema2 = json.loads(m2.group(1))
    assert len(skema2["mainEntity"]) == len(tanya_lama) + len(FAQ_TAMBAHAN), "jumlah item skema tidak cocok"
    isi_tanpa_skrip = re.sub(r"<script[^>]*>.*?</script>", " ", baru, flags=re.S | re.I)
    for i, q in enumerate(skema2["mainEntity"]):
        assert f"<h3>{q['name']}</h3>" in isi_tanpa_skrip, f"pertanyaan skema tidak tampil: {q['name']}"
        assert norm(q["acceptedAnswer"]["text"]) in norm(isi_tanpa_skrip), f"jawaban skema tidak ada di isi: {q['name']}"
    info["faq_baru"] = len(skema2["mainEntity"])

    # kalimat lama harus tetap ada (spasi dinormalkan, tag dibuang di KEDUA sisi lebih dulu,
    # lalu dipisah per kalimat dari teks yang sudah rapi supaya batas blok tidak menggabungkan kalimat)
    lama_tanpa_skrip = re.sub(r"<script[^>]*>.*?</script>", " ", lama, flags=re.S | re.I)
    teks_lama = norm(lama_tanpa_skrip)
    kal = [k for k in re.split(r"(?<=[.?!])\s+", teks_lama) if len(k.split()) > 4]
    teks_baru = norm(re.sub(r"<script[^>]*>.*?</script>", " ", baru, flags=re.S | re.I))
    hilang = [k[:90] for k in kal if k not in teks_baru]
    info["kalimat_lama"] = len(kal)
    info["hilang"] = hilang
    assert not hilang, f"kalimat lama hilang: {hilang[:3]}"

    # pemeriksaan gaya
    teks_norm = re.sub(r"<[^>]+>", " ", BLOK_BARU + " " + " ".join(q + " " + a for q, a in FAQ_TAMBAHAN))
    for nama, pola in {"em dash": "\u2014", "kutip melengkung": "[\u201c\u201d\u2018\u2019]", "CJK": "[\u3000-\u9fff]",
                       "tanda seru": "!", "strong": "<strong>"}.items():
        assert not re.search(pola, teks_norm), f"pola dilarang ({nama})"
    for frasa in ["di era digital", "tidak hanya", "tak dapat dipungkiri", "perlu dicatat bahwa", "sangat penting untuk"]:
        assert frasa not in teks_norm.lower(), f"frasa AI: {frasa}"
    return baru, info


def main():
    terapkan = "--apply" in sys.argv
    st, p = rest(f"/wp-json/wp/v2/posts/{POST_ID}?context=edit&_fields=id,slug,link,title,content,meta")
    assert st == 200, f"gagal ambil post: {st} {p}"
    lama = p["content"]["raw"]
    judul_lama = p["title"]["raw"]
    fokus_lama = (p.get("meta") or {}).get("_yoast_wpseo_focuskw")
    meta_lama = (p.get("meta") or {}).get("_yoast_wpseo_metadesc")

    baru, info = bangun(lama)
    print(f"post #{p['id']}  {p['link']}")
    print(f"judul: {judul_lama}")
    print(f"kata sebelum: {kata(lama)} (tanpa skema {kata(lama, False)}) -> "
          f"sesudah: {kata(baru)} (tanpa skema {kata(baru, False)})")
    print(f"H2 {lama.count('<h2')} -> {baru.count('<h2')} | H3 {lama.count('<h3')} -> {baru.count('<h3')}")
    print(f"FAQ lama {len(info['faq_lama'])} -> {info['faq_baru']} | <p><p> rusak {info['p_nested']} -> {baru.count('</p></p>')}")
    print(f"kalimat lama diperiksa: {info['kalimat_lama']}, hilang: {len(info['hilang'])}")
    wa_n = len(re.findall(r"wa\.me/\d{6,}", baru))
    print(f"tombol WA: {wa_n} | skema FAQPage: {baru.count('FAQPage')}")

    if not terapkan:
        open(f"{FOLDER}/post_{POST_ID}_SESUDAH_dryrun.html", "w").write(baru)
        print("\nDRY RUN selesai. Jalankan dengan --apply untuk menulis.")
        return
    st, resp = rest(f"/wp-json/wp/v2/posts/{POST_ID}", {"content": baru}, method="POST")
    print(f"\nPOST REST -> HTTP {st}")
    json.dump(resp if isinstance(resp, dict) else {"raw": str(resp)},
              open(f"{FOLDER}/post_{POST_ID}_POSTRESP.json", "w"), ensure_ascii=False, indent=1)
    if st != 200:
        print("GAGAL:", str(resp)[:400])
        sys.exit(1)
    # baca ulang dari API
    st2, p2 = rest(f"/wp-json/wp/v2/posts/{POST_ID}?context=edit&_fields=id,slug,link,title,content,meta")
    ulang = p2["content"]["raw"]
    verif = {
        "http_baca_ulang": st2,
        "isi_identik_dengan_yang_dikirim": ulang == baru,
        "kata_api": kata(ulang),
        "judul_sama": (p2["title"]["raw"] == judul_lama),
        "slug_sama": (p2["slug"] == p["slug"]),
        "fokus_sama": ((p2.get("meta") or {}).get("_yoast_wpseo_focuskw") == fokus_lama),
        "metadesc_sama": ((p2.get("meta") or {}).get("_yoast_wpseo_metadesc") == meta_lama),
        "faq_tampil": len(re.findall(r"<h3>[^<]*\?</h3>", ulang)),
        "item_skema": len(json.loads(re.search(r'<script type="application/ld\+json">(\{.*?\})</script>',
                                               ulang, re.S).group(1))["mainEntity"]),
        "tombol_wa": len(re.findall(r"wa\.me/\d{6,}", ulang)),
    }
    json.dump(verif, open(f"{FOLDER}/post_{POST_ID}_VERIFIKASI.json", "w"), ensure_ascii=False, indent=1)
    print(json.dumps(verif, ensure_ascii=False, indent=1))
    open(f"{FOLDER}/post_{POST_ID}_SESUDAH.html", "w").write(ulang)
    if not verif["isi_identik_dengan_yang_dikirim"]:
        print("PERINGATAN: isi hasil baca ulang tidak identik!")
        sys.exit(1)


if __name__ == "__main__":
    FOLDER_ADA = __import__("os").path.isdir(FOLDER)
    if not FOLDER_ADA:
        print(f"folder {FOLDER} belum ada, buat dulu."); sys.exit(1)
    # simpan salinan SEBELUM bila belum ada
    import os
    if "--apply" in sys.argv and not os.path.exists(f"{FOLDER}/post_{POST_ID}_SEBELUM.html"):
        st, p = rest(f"/wp-json/wp/v2/posts/{POST_ID}?context=edit&_fields=id,slug,link,title,content,meta")
        json.dump(p, open(f"{FOLDER}/post_{POST_ID}_SEBELUM.json", "w"), ensure_ascii=False)
        open(f"{FOLDER}/post_{POST_ID}_SEBELUM.html", "w").write(p["content"]["raw"])
        print("salinan SEBELUM disimpan.")
    main()
