# SUPERAPP ACADEMY V20.3 — RESET & LANJUTKAN CAT TANPA KEHILANGAN JAWABAN

Basis: **V20.2**; CodeIgniter 4, PHP 8.2+, MySQL/MariaDB, cPanel tanpa Composer jika instalasi V20.2 sudah berjalan.

## Isi perubahan

1. **Resume:** POST `uji/{jenis}/{id}/mulai` sekarang tetap memakai `attempts` yang `in_progress` selama `expires_at` belum lewat. Dashboard dan Riwayat Pengerjaan menampilkan **Lanjutkan**. Waktu CAT **tetap berjalan** sejak pertama kali mulai, meskipun peserta menutup browser.
2. **Jawaban resmi** tetap berada di `attempt_answers`. Saat dibuka kembali, server mengirim jawaban tersimpan untuk setiap nomor, posisi pertama belum dijawab, dan masa akhir ujian aslinya.
3. **Autosave teks:** untuk ujian resmi, essay/short_text otomatis dikirim setelah berhenti mengetik sekitar 650 ms. Bisa juga pilih **Simpan Sekarang**. Pilihan ujian resmi tetap dikirim tiap klik.
4. **Draf latihan:** pada `practice` soal majemuk dan uraian, pilihan/teks yang belum dikonfirmasi masuk tabel `attempt_drafts`. Draf **belum dinilai** dan belum membuka pembahasan. Tombol Periksa/Konfirmasi menyimpan jawaban final ke `attempt_answers`, menghapus draf, dan membuka pembahasan.
5. **Koneksi bermasalah:** permintaan diserialkan agar token CSRF tidak berlomba, tersedia indikator tersimpan/belum tersimpan, tombol *Coba Simpan Lagi*, percobaan ulang saat online, dan backup lokal sementara bila server belum mengonfirmasi. Hanya data dengan indikator **✓ Tersimpan di server** yang aman lintas perangkat. Draft offline pada browser/perangkat yang berbeda tidak dapat dipulihkan sampai sinkronisasi berhasil.
6. **Reset admin:** buka **Panel Admin → Peserta → Akses & Reset CAT**. Pilih percobaan, isi **alasan wajib**, konfirmasi. Status menjadi `reset` tanpa menghapus snapshot/jawaban lama. Reset masuk `audit_logs` dan peserta menerima notifikasi. Attempt dengan status reset tidak dihitung terhadap `max_attempts`, tidak masuk ranking, dan peserta bisa mulai ulang **jika produk/jadwal/hak akses masih aktif**.
7. **Tidak ada reset mandiri oleh peserta**, agar batas percobaan berbayar tidak dapat dilewati.

## Pemasangan pada V20.2 yang sudah memiliki database

1. **Backup** seluruh database lewat phpMyAdmin dan root aplikasi. Simpan `.env`, `writable/secure/route-id.key`, `writable/uploads`, `public/uploads` dan seluruh file peserta.
2. Buka **phpMyAdmin → database aplikasi → SQL** dan jalankan **hanya** `database/UPGRADE_V20_2_KE_V20_3.sql` **satu kali**. Skrip ini memperbarui enum `attempts.status` dan menambah `attempt_drafts`; tidak menghapus attempts, skor, pembelian, atau jawaban lama.
3. Upload ZIP **PATCH V20.3** ke **root CodeIgniter 4** (direktori yang berisi `app/`, `public/`, `system/`, `writable/`) lalu **Extract → Overwrite/Replace**.
4. Jika ingin **FULL SOURCE**, bisa timpa source V20.2 memakai ZIP full setelah backup, tetapi **jangan menimpa `.env`, kunci secure atau file upload produksi**. Jangan mengimpor ulang `database/IMPORT_SEKALI.sql` ke database yang sudah berisi data.
5. Bersihkan cache publik dengan fitur admin. Buka halaman CAT dan tekan `Ctrl + F5` untuk memuat skrip baru.

## Tes wajib di staging

1. Mulai tryout berisi soal pilihan, pilih jawaban; pastikan **✓ Tersimpan di server**. Tutup tab, login lagi, buka **Riwayat → Lanjutkan**; pilihan sama dan waktu berkurang sesuai waktu nyata.
2. Ketik uraian panjang; tunggu indikator simpan; tutup tab lalu masuk lagi. Uraian tetap ada.
3. Latihan pilihan majemuk: centang beberapa opsi, **jangan** tekan Periksa; tutup tab; masuk lagi dan cek opsi yang tadi dipilih masih muncul tanpa membocorkan pembahasan.
4. Cabut internet saat mengetik; tombol ulang dan indikator merah harus muncul. Saat online lagi, tunggu **✓ Tersimpan di server**. Cek dari browser lain agar memastikan data benar-benar ada di server.
5. Saat waktu habis, server menilai jawaban yang sudah disimpan, tidak memperbarui timer saat login lagi.
6. Reset melalui admin dengan alasan minimal 10 karakter, cek `audit_logs`, notifikasi peserta, riwayat `reset`, lalu mulai ulang jika jadwal dan hak akses masih aktif.
7. Uji ketika batas percobaan = 1: tanpa reset percobaan kedua ditolak; setelah reset oleh admin, percobaan baru diizinkan.
8. Uji peran peserta: URL POST reset harus **ditolak oleh filter admin**. Periksa akses terhadap attempt milik user lain.

## Batasan dan kehati-hatian

- Data belum diterima server saat koneksi offline **tidak dapat dijamin aman lintas perangkat**. Cadangan browser hanya bantuan sementara, bukan pengganti simpan ke server.
- Mengembalikan akses dari reset **tidak otomatis memperpanjang tanggal kedaluwarsa produk, penutupan tryout, membership, ataupun masa registrasi**. Admin dapat memakai menu Kelola Akses jika diperlukan dan sah.
- File `attempts` berstatus `reset` disimpan untuk audit. Hasil lama yang telah direset tidak ditampilkan sebagai hasil aktif atau peringkat.
- SQL harus dijalankan **sebelum** menimpa kode supaya aplikasi tidak gagal akibat tabel `attempt_drafts` belum tersedia.
- Jika muncul Whoops, cek `writable/logs` dan PHP error log hosting; pastikan folder cache/log memiliki izin tulis yang benar.
- Kunci `.env` / Google OAuth / password database **tidak termasuk** di dalam ZIP.

## Pengujian lokal

```shell
php tests/smoke_cat_resume_reset_v20_3.php
php tests/smoke_v20.php
php tests/smoke_cache_log_v20_2.php
```

Static lint dan smoke test tidak menggantikan pengujian MySQL, CSRF session, dan browser nyata di staging.
