# Rancangan Sistem Informasi Akademik Madrasah Syubbanul Wathon
## Fase 0 (Audit) & Fase 1 (Autentikasi, RBAC, Master Data Dasar)

> Dokumen ini adalah hasil analisis sebelum penulisan kode, sesuai permintaan.
> Cakupan: Fase 0 dan Fase 1 saja. Fase 2–8 akan dirancang detail saat gilirannya
> tiba, mengikuti prinsip "jangan lanjut ke fase berikutnya sebelum fase
> sebelumnya stabil" yang sudah ditetapkan.

---

## 1. Audit Sistem Lama (Fase 0)

### 1.1 Yang sudah berjalan stabil di PresenSync
| Komponen | File | Fungsi |
|---|---|---|
| Konfigurasi DB | `config.php` | Koneksi PDO, timezone Asia/Jakarta |
| API perizinan | `api.php` | Login (NIPY saja, tanpa password), simpan/edit/hapus izin, generate nomor surat |
| API absensi RFID | `api_absensi.php` | Tap kartu, kelola kartu, kalender libur, setting kelas |
| Aplikasi guru | `index.html` | Input izin, Hari Ini, Rekap, Klasemen, Admin |
| Kiosk tap kartu | `tap.html` | Halaman tempel kartu di gerbang |

### 1.2 Struktur tabel yang SUDAH ADA (tidak boleh rusak)
```
guru            (id, nama, nipy)
kelas           (id, nama_kelas, tingkat, sesi)
siswa           (id, kelas, nama_siswa)
log_izin        (id, timestamp, uuid, tanggal, no_surat, siswa, kelas, tingkat, keterangan, nipy, guru, bulan, tahun)
counter_surat   (bulan, tahun, nomor_terakhir)
kartu_rfid      (id, kartu_uid, nama_siswa, kelas)
absensi_hadir   (id, kartu_uid, siswa, kelas, tanggal, waktu_tap)
kalender_libur  (id, tanggal, keterangan, tingkat)
```
Catatan penting: tabel `siswa` dan `kartu_rfid`/`absensi_hadir` saat ini **mencocokkan siswa lewat teks** (`nama_siswa` + `kelas`), bukan lewat `id`/foreign key. Ini akan dirapikan bertahap di fase-fase mendatang, tapi TIDAK diubah di Fase 1 supaya PresenSync tidak terganggu.

### 1.3 Tindakan wajib sebelum Fase 1 dijalankan di server produksi
1. **Backup penuh database** lewat phpMyAdmin → Export → format SQL, simpan di tempat aman (bukan di server yang sama).
2. **Verifikasi skema live**: karena riwayat pengembangan sudah melalui banyak sesi, mohon export ulang struktur tabel saat ini (Export → Structure only) dan saya bandingkan dengan dokumen ini sebelum migrasi dijalankan — untuk memastikan tidak ada drift dari asumsi saya.
3. **Batalkan** rencana `migrasi_akun_sia.sql` versi lama (role tunggal per-guru) yang sempat saya susun di sesi sebelumnya — digantikan skema RBAC many-to-many di bawah ini.

---

## 2. Kebutuhan Fungsional (ringkas, Fase 1)
- Satu pengguna (Pembimbing) dapat memegang lebih dari satu role sekaligus.
- Login Pembimbing: NIPY + password (awal = tanggal lahir format `DDMMYY`).
- Login siswa: NIS + password (awal = tanggal lahir format `DDMMYY`).
- Wajib ganti password di login pertama.
- Rate limiting percobaan login (lockout sementara).
- Audit log untuk aksi penting (login, perubahan role, perubahan data master).
- Session server-side dengan timeout.
- Data Pembimbing & siswa yang sudah ada di PresenSync **tidak boleh hilang atau perlu re-entry** — hanya ditambah kolom baru.

## 3. Kebutuhan Nonfungsional (Fase 1)
- Tidak ada downtime PresenSync selama migrasi (migrasi bersifat `ALTER TABLE ADD COLUMN`, bukan `DROP`/`RENAME`).
- Kredensial database tetap hanya di `config.php` (di luar akses publik), tidak pernah di frontend.
- Semua query pakai prepared statement (PDO, sudah jadi kebiasaan di codebase ini).
- Password di-hash dengan `password_hash()` (bcrypt/argon2i bawaan PHP), tidak pernah plaintext.
- Endpoint API memvalidasi role di backend, bukan hanya menyembunyikan menu di frontend.

---

## 4. Matriks Role & Permission (Fase 1 — fondasi, akan meluas di fase berikut)

| Aksi | Kepala Madrasah | Admin | Pengurus | Wali Kelas | Pembimbing | Siswa |
|---|:---:|:---:|:---:|:---:|:---:|:---:|
| Login ke SIA | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Kelola akun & role pengguna lain | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ |
| Kelola master data siswa (semua kelas) | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ |
| Kelola data siswa kelasnya sendiri | ✅ | ✅ | ❌ | ✅ | ❌ | ❌ |
| Kelola master kelas & tahun ajaran | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ |
| Lihat audit log | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ |
| Ubah profil & password sendiri | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Akses modul PresenSync (izin, tap) | ✅ | ✅ | ✅ | ✅ | ✅ | (lihat data sendiri, fase 3) |

*(Matriks penuh untuk nilai, pesan, rapor, dsb menyusul di fase terkait — dibangun saat modulnya digarap, supaya tabel `permissions` tidak berisi entri untuk fitur yang belum ada.)*

---

## 5. Sitemap per Role (kerangka navigasi, Fase 1 hanya menyiapkan shell + menu Presensi)

```
Kepala Madrasah / Admin
├── Dashboard (placeholder Fase 1, isi penuh di Fase 4)
├── Presensi (PresenSync — tautan ke modul existing)
├── Master Data
│   ├── Data Pembimbing
│   ├── Data Siswa
│   └── Setting Kelas & Tahun Ajaran
├── Audit Log
└── Profil Saya

Pengurus / Wali Kelas / Pembimbing
├── Dashboard (placeholder)
├── Presensi (PresenSync)
└── Profil Saya

Siswa
├── Dashboard (placeholder)
├── Kehadiran Saya (baca dari PresenSync, Fase 3)
└── Profil Saya
```

---

## 6. ERD Fase 1 (Mermaid)

```mermaid
erDiagram
  USERS ||--o{ USER_ROLES : memiliki
  ROLES ||--o{ USER_ROLES : mencakup
  USERS ||--o| GURU : "terhubung ke (pembimbing)"
  USERS ||--o| SISWA : "terhubung ke (siswa)"
  KELAS ||--o{ SISWA : menaungi
  KELAS ||--o{ HOMEROOM_ASSIGNMENTS : diampu
  GURU ||--o{ HOMEROOM_ASSIGNMENTS : "menjadi wali kelas"
  ACADEMIC_YEARS ||--o{ SEMESTERS : memiliki
  SEMESTERS ||--o{ HOMEROOM_ASSIGNMENTS : berlaku
  USERS ||--o{ AUDIT_LOGS : melakukan

  USERS {
    int id PK
    string username
    string password_hash
    enum user_type
    tinyint wajib_ganti_password
    int gagal_login
    datetime terkunci_sampai
    enum status
    datetime last_login_at
  }
  ROLES {
    int id PK
    string kode
    string nama_tampil
  }
  USER_ROLES {
    int user_id FK
    int role_id FK
  }
  GURU {
    int id PK
    int user_id FK
    string nama
    string nipy
    date tanggal_lahir
    enum status
  }
  SISWA {
    int id PK
    int user_id FK
    string nama_siswa
    string nis
    string nisn
    string kelas
    date tanggal_lahir
    enum status
  }
  KELAS {
    int id PK
    string nama_kelas
    tinyint tingkat
    enum sesi
    int academic_year_id FK
    enum status
  }
  ACADEMIC_YEARS {
    int id PK
    string nama
    enum status
  }
  SEMESTERS {
    int id PK
    int academic_year_id FK
    enum nama
    enum status
  }
  HOMEROOM_ASSIGNMENTS {
    int id PK
    int kelas_id FK
    int guru_id FK
    int semester_id FK
    enum status
  }
  AUDIT_LOGS {
    int id PK
    int user_id FK
    string aksi
    string entitas
    int entitas_id
    json data_lama
    json data_baru
    string ip_address
    datetime created_at
  }
```

---

## 7. Rancangan Tabel SQL (Fase 1)

### 7.1 Tabel baru (murni tambahan, tidak menyentuh data lama)

```sql
CREATE TABLE users (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    username VARCHAR(50) NOT NULL,
    password_hash VARCHAR(255) NOT NULL,
    user_type ENUM('pembimbing','siswa') NOT NULL,
    wajib_ganti_password TINYINT(1) NOT NULL DEFAULT 1,
    gagal_login INT UNSIGNED NOT NULL DEFAULT 0,
    terkunci_sampai DATETIME NULL,
    status ENUM('aktif','nonaktif') NOT NULL DEFAULT 'aktif',
    last_login_at DATETIME NULL,
    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    UNIQUE KEY uniq_username (username)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE roles (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    kode VARCHAR(30) NOT NULL,
    nama_tampil VARCHAR(50) NOT NULL,
    UNIQUE KEY uniq_kode (kode)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

INSERT INTO roles (kode, nama_tampil) VALUES
    ('kepala_madrasah', 'Kepala Madrasah'),
    ('admin', 'Admin'),
    ('pengurus', 'Pengurus'),
    ('wali_kelas', 'Wali Kelas'),
    ('pembimbing', 'Pembimbing'),
    ('siswa', 'Siswa');

CREATE TABLE user_roles (
    user_id INT UNSIGNED NOT NULL,
    role_id INT UNSIGNED NOT NULL,
    PRIMARY KEY (user_id, role_id),
    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
    FOREIGN KEY (role_id) REFERENCES roles(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE academic_years (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    nama VARCHAR(20) NOT NULL,
    status ENUM('aktif','arsip') NOT NULL DEFAULT 'arsip',
    UNIQUE KEY uniq_nama (nama)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE semesters (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    academic_year_id INT UNSIGNED NOT NULL,
    nama ENUM('ganjil','genap') NOT NULL,
    status ENUM('aktif','ditutup','arsip') NOT NULL DEFAULT 'arsip',
    FOREIGN KEY (academic_year_id) REFERENCES academic_years(id),
    UNIQUE KEY uniq_tahun_semester (academic_year_id, nama)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE homeroom_assignments (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    kelas_id INT UNSIGNED NOT NULL,
    guru_id INT UNSIGNED NOT NULL,
    semester_id INT UNSIGNED NOT NULL,
    status ENUM('aktif','berakhir') NOT NULL DEFAULT 'aktif',
    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (kelas_id) REFERENCES kelas(id),
    FOREIGN KEY (guru_id) REFERENCES guru(id),
    FOREIGN KEY (semester_id) REFERENCES semesters(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE audit_logs (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    user_id INT UNSIGNED NULL,
    aksi VARCHAR(100) NOT NULL,
    entitas VARCHAR(50) NOT NULL,
    entitas_id INT UNSIGNED NULL,
    data_lama JSON NULL,
    data_baru JSON NULL,
    ip_address VARCHAR(45) NULL,
    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    KEY idx_user (user_id),
    KEY idx_entitas (entitas, entitas_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
```

### 7.2 Perubahan tabel lama (HANYA `ADD COLUMN`, tidak ada `DROP`/`RENAME`)

```sql
-- guru tetap bernama "guru" secara internal (kompatibilitas), label UI "Pembimbing"
ALTER TABLE guru
    ADD COLUMN user_id INT UNSIGNED NULL AFTER id,
    ADD COLUMN tanggal_lahir DATE NULL AFTER nipy,
    ADD COLUMN status ENUM('aktif','nonaktif') NOT NULL DEFAULT 'aktif' AFTER tanggal_lahir,
    ADD UNIQUE KEY uniq_user (user_id),
    ADD CONSTRAINT fk_guru_user FOREIGN KEY (user_id) REFERENCES users(id);

ALTER TABLE siswa
    ADD COLUMN user_id INT UNSIGNED NULL AFTER id,
    ADD COLUMN nis VARCHAR(30) NULL AFTER nama_siswa,
    ADD COLUMN nisn VARCHAR(30) NULL AFTER nis,
    ADD COLUMN tanggal_lahir DATE NULL AFTER nisn,
    ADD COLUMN nama_ayah VARCHAR(150) NULL,
    ADD COLUMN nama_ibu VARCHAR(150) NULL,
    ADD COLUMN alamat TEXT NULL,
    ADD COLUMN kamar VARCHAR(50) NULL,
    ADD COLUMN email_sekolah VARCHAR(150) NULL,
    ADD COLUMN no_wa VARCHAR(30) NULL,
    ADD COLUMN email_ortu VARCHAR(150) NULL,
    ADD COLUMN status ENUM('aktif','lulus','pindah','keluar','diarsipkan') NOT NULL DEFAULT 'aktif',
    ADD COLUMN tahun_masuk YEAR NULL,
    ADD COLUMN updated_at DATETIME NULL,
    ADD COLUMN updated_by INT UNSIGNED NULL,
    ADD UNIQUE KEY uniq_user (user_id),
    ADD UNIQUE KEY uniq_nis (nis),
    ADD CONSTRAINT fk_siswa_user FOREIGN KEY (user_id) REFERENCES users(id);

ALTER TABLE kelas
    ADD COLUMN academic_year_id INT UNSIGNED NULL,
    ADD COLUMN status ENUM('aktif','arsip') NOT NULL DEFAULT 'aktif',
    ADD CONSTRAINT fk_kelas_tahun FOREIGN KEY (academic_year_id) REFERENCES academic_years(id);
```

**Catatan UID RFID**: sesuai instruksi, kartu RFID memakai data yang sudah ada di PresenSync (`kartu_rfid`, `absensi_hadir`) — tidak dibuat ulang. Kolom `kartu_uid` di siswa akan disinkronkan sebagai referensi baca saja pada fase integrasi PresenSync (Fase 3), bukan di Fase 1.

---

## 8. Alur Login

1. Pengguna membuka `sia.masw.sch.id`, memasukkan NIPY (Pembimbing) atau NIS (siswa) + password.
2. Backend cek: apakah `users.username` cocok? Kalau tidak ketemu → pesan generik "NIPY/NIS atau password salah" (tidak membocorkan mana yang salah, mencegah user enumeration).
3. Cek `terkunci_sampai` — kalau masih terkunci, tolak dengan pesan sisa waktu.
4. Kalau `password_hash` cocok → reset `gagal_login` ke 0, catat `last_login_at`, mulai session, cek `wajib_ganti_password`:
   - Jika 1 → paksa ke halaman ganti password sebelum bisa akses menu lain.
   - Jika 0 → lanjut ke dashboard sesuai role.
5. Kalau salah → `gagal_login += 1`. Jika mencapai 5x → set `terkunci_sampai = NOW() + 15 menit`.
6. Setiap login (berhasil/gagal) dicatat ke `audit_logs`.

**Bootstrap password awal** (khusus baris yang `password_hash` masih kosong, migrasi dari data lama): sistem membandingkan input password dengan tanggal lahir format `DDMMYY` secara langsung sekali saja, lalu men-hash dan menyimpannya, plus set `wajib_ganti_password = 1`. Ini menghindari harus generate hash massal manual untuk ratusan akun sekaligus.

---

## 9. Daftar Endpoint API (Fase 1)

| Endpoint | Method | Untuk | Role |
|---|---|---|---|
| `auth.php?action=login` | POST | Login NIPY/NIS + password | Publik |
| `auth.php?action=logout` | POST | Logout, hancurkan session | Semua |
| `auth.php?action=ganti_password` | POST | Ganti password (wajib di login pertama) | Semua |
| `auth.php?action=whoami` | GET | Ambil data sesi aktif (nama, role, dsb) | Semua (butuh sesi) |
| `master_pembimbing.php?action=list` | GET | Daftar Pembimbing + role | Kepala Madrasah, Admin |
| `master_pembimbing.php?action=simpan` | POST | Tambah/edit Pembimbing + assign role | Kepala Madrasah, Admin |
| `master_siswa.php?action=list` | GET | Daftar siswa (filter kelas/tingkat/status) | Kepala Madrasah, Admin, Wali Kelas (kelasnya saja) |
| `master_siswa.php?action=import` | POST | Import NIS/NISN/tanggal lahir massal | Kepala Madrasah, Admin |
| `master_kelas.php?action=*` | POST/GET | Kelola kelas, tahun ajaran, wali kelas | Kepala Madrasah, Admin |
| `audit.php?action=list` | GET | Lihat audit log (filter tanggal/user/aksi) | Kepala Madrasah, Admin |

Semua endpoint memverifikasi role di backend lewat helper `requireRole([...])` sebelum memproses request — bukan hanya menyembunyikan tombol di frontend.

---

## 10. Alur Migrasi (Fase 0 → Fase 1)

1. Backup database (manual, Anda lakukan lewat phpMyAdmin).
2. Saya kirim ulang `schema.sql`/`schema_absensi.sql` gabungan terbaru untuk Anda bandingkan dengan struktur live — konfirmasi tidak ada perbedaan.
3. Jalankan migrasi Fase 1 (`ALTER TABLE` + `CREATE TABLE` di atas) lewat phpMyAdmin.
4. Jalankan skrip bootstrap (PHP CLI atau lewat halaman admin sekali-jalan) untuk membuat baris `users` untuk setiap `guru`/`siswa` yang sudah ada, berdasarkan NIPY/NIS.
5. Tetapkan role awal manual untuk minimal 1 akun Kepala Madrasah dan 1 Admin (lewat query langsung, karena belum ada UI-nya di titik ini).
6. Uji login dengan akun uji coba dulu sebelum broadcast ke seluruh Pembimbing/siswa.
7. PresenSync (`api.php`, `api_absensi.php`, `index.html`, `tap.html`) **tidak diubah sama sekali** di Fase 1 — tetap berjalan seperti biasa berdampingan.

## 11. Risiko Keamanan & Mitigasi

| Risiko | Mitigasi |
|---|---|
| Tanggal lahir mudah ditebak sebagai password | Wajib ganti password di login pertama + rate limiting |
| Brute force NIPY/NIS | Lockout 15 menit setelah 5x gagal, dicatat di audit log |
| Password bocor dari database | `password_hash()` (bcrypt), tidak pernah plaintext |
| Session hijacking | Session timeout, regenerate session ID saat login |
| Privilege escalation lewat manipulasi request | Validasi role di backend tiap endpoint, bukan cuma sembunyikan menu |
| SQL Injection | PDO prepared statement (sudah konsisten dipakai di codebase ini) |
| Kredensial DB bocor ke publik | Tetap di `config.php` luar `public_html` bila memungkinkan, atau minimal di luar direktori yang di-serve langsung |

## 12. Roadmap (mengikuti urutan yang sudah Anda tetapkan)
Fase 0 → 1 (sekarang) → 2 (kurikulum/jam mengajar) → 3 (integrasi PresenSync) → 4 (dashboard/pengumuman/pesan) → 5 (jadwal/piket/ekskul) → 6 (nilai) → 7 (rapor) → 8 (layanan digital/perizinan Pembimbing).

## 13. Asumsi
- Tabel `guru` dan `siswa` tetap dipakai sebagai tabel profil utama (bukan tabel `pembimbing_profiles`/`student_profiles` baru terpisah) — sesuai izin eksplisit Anda untuk pertahankan nama internal demi kompatibilitas.
- "NIS SW" pada spesifikasi merujuk ke nomor induk siswa madrasah ini (bukan sistem eksternal) — akan dipakai sebagai `username` untuk siswa.
- Domain email sekolah siswa: akan dikonfirmasi (`@masw.sch.id` atau `@mtssw.sch.id` disebut keduanya di spesifikasi).

## 14. Keputusan yang Masih Perlu Ditetapkan
1. Konfirmasi domain email resmi siswa: `@masw.sch.id` atau `@mtssw.sch.id`?
2. Siapa (NIPY berapa) yang jadi akun Kepala Madrasah & Admin pertama untuk bootstrap?
3. Apakah field `NIS` yang dipakai sudah tersedia datanya di tempat lain (misal data induk madrasah), atau perlu diinput ulang dari nol seperti kasus kartu RFID kemarin?
