Repository: mrdavis-dev/clisys
Author: mrdavis-dev
## Objective
Transform the system into a multi-specialty SaaS: the odontogram becomes an optional module activatable per clinic, a generic clinical history is added for other specialties, and the subscription lifecycle is implemented.
## Precondition
Phase 2 (multi-tenancy with `clinic_id`) must be merged.
## Tasks
### Modules by specialty
- [ ] Create `specialties` table (`id`, `name`, `slug`) — e.g. dental, general_medicine, pediatrics
- [ ] Add `clinics.specialty_id FK → specialties.id`
- [ ] Create `modules` table (`id`, `name`, `slug`) — e.g. odontogram, clinical_notes, lab_orders
- [ ] Create `clinic_modules` table (`clinic_id`, `module_id`) — join table for feature flags
- [ ] Implement `Tenant::hasModule(string $slug): bool` in `admin/core/Tenant.php`
- [ ] Wrap odontogram in `if (Tenant::hasModule('odontogram'))` in `menu.php` and `odontograma.php`
- [ ] Hide Odontogram link in `menu.php` for clinics without the module
### Generic clinical history
- [ ] Create `clinical_notes` table (`id`, `clinic_id`, `patient_id`, `author_id`, `content` TEXT, `created_at`) — replaces/complements `consulta` for non-dental clinics
- [ ] Create `admin/notas.php` + `admin/insert_nota.php` — form and handler for free clinical notes
- [ ] The `pacientes` table has very dental-specific fields (hygiene habits, etc.) — migrate to generic history + extended fields in JSON or EAV table `patient_attributes`
### Plans and billing
- [ ] Create `plans` table (`id`, `name`, `max_patients`, `max_users`, `modules` JSON, `price_monthly`)
- [ ] Add `clinics.plan_id FK → plans.id` and `clinics.plan_expires_at`
- [ ] Implement `Tenant::withinLimit(string $resource): bool` — block insertion if plan limit exceeded
- [ ] Upgrade page visible when limit is reached
### Public onboarding
- [ ] Create public page `register.php` (root, not `/admin`) — new clinic registration form: name, specialty, desired subdomain, email, first admin password
- [ ] Handler `register_clinic.php` — creates record in `clinics` + admin user + welcome email
- [ ] Subdomain availability validation (AJAX)
### Image storage (odontogram)
- [ ] Odontogram images are BLOBs in `consulta.imageData` — migrate to disk storage per clinic: `storage/{clinic_id}/{patient_cedula}/{filename}`
- [ ] Update `insert_odo.php` to save on disk and store only path in DB
- [ ] Update `consulta_odo.php` to serve from path, not base64 BLOB
### Audit log
- [ ] Create `audit_log` table (`id`, `clinic_id`, `user_id`, `action`, `table_name`, `record_id`, `created_at`)
- [ ] Log critical insertions and deletions (patients, payments, users)
### Email per clinic
- [ ] Add to `clinics` table: `smtp_host`, `smtp_user`, `smtp_password`, `smtp_port`, `smtp_from`
- [ ] Modify `insert_pagos_send.php` to read SMTP config from active clinic instead of global `.env` (fallback to `.env`)
## Verification
- Dental clinic: sees odontogram in menu and can save treatments
- General medicine clinic: does not see odontogram, sees generic clinical notes
- Public registration: create new clinic from scratch, receive welcome email, immediate login
- Free plan with 50 patient limit: adding 51 shows upgrade message
- Odontogram images served from disk, not DB
Source: GITHUB