Skip to main content

Overview

OdontologyApp uses MySQL as its primary database with a well-structured relational schema. The database is accessed through a connection pool and primarily uses stored procedures for data operations.

Database Connection

Connection Pool (src/lib/server/db.js:1-13):
import mysql from "mysql2/promise";
import { DB_HOST, DB_USER, DB_PASS, DB_NAME } from "$env/static/private";

export const pool = mysql.createPool({
  host: DB_HOST,
  user: DB_USER,
  password: DB_PASS,
  database: DB_NAME,
  waitForConnections: true,
  connectionLimit: 10,
  queueLimit: 0,
});

Configuration

  • Connection limit: 10 concurrent connections
  • Queue limit: Unlimited (0 = no limit)
  • Wait for connections: true (requests wait for available connection)
  • Promise-based: Uses mysql2/promise for async/await support

Environment Variables

Required in .env:
DB_HOST=localhost
DB_USER=your_username
DB_PASS=your_password
DB_NAME=ontology_db

Core Tables

Branches (Sucursales)

Stores clinic branch/location information:
ColumnTypeConstraintsDescription
idINTPRIMARY KEY, AUTO_INCREMENTUnique identifier
nameVARCHAR(100)NOT NULLBranch name
addressVARCHAR(255)Physical address
iconVARCHAR(50)Icon/emoji for UI
statusENUM’active’, ‘inactive’Branch status
created_atTIMESTAMPDEFAULT CURRENT_TIMESTAMPCreation timestamp
Example data:
INSERT INTO branches (name, address, icon, status) VALUES 
('Sucursal Central', 'Av. Providencia 1234, Santiago', '📍', 'active'),
('Sucursal Norte', 'Av. Recoleta 456, Santiago', '🏢', 'active');

Users

System users (administrators, doctors, secretaries):
ColumnTypeConstraintsDescription
idINTPRIMARY KEY, AUTO_INCREMENTUnique identifier
nameVARCHAR(100)NOT NULLFull name
usernameVARCHAR(50)UNIQUE, NOT NULLLogin username
emailVARCHAR(100)UNIQUEEmail address
passwordVARCHAR(255)NOT NULLBcrypt hashed password
roleENUM’admin’, ‘doctor’, ‘secretary’User role
initialsVARCHAR(5)User initials for UI
branch_idINTFOREIGN KEY → branches(id)Assigned branch
statusENUM’active’, ‘inactive’Account status
created_atTIMESTAMPDEFAULT CURRENT_TIMESTAMPCreation timestamp
Default users:
INSERT INTO users (name, username, email, password, role, initials, branch_id) VALUES 
-- admin / admin123
('Andrea Rojas', 'admin', '[email protected]', '$2b$10$mo1jWoA/zRY1wN6x6hsfgeAWNAOE7cLTXseMBiO3M4JSvhjvhImtC', 'admin', 'AR', 1),
-- doctor / doctor123
('Dr. Carlos Soto', 'doctor', '[email protected]', '$2b$10$aCdT80m.xivVAK0rWf.rD..SyWRRc6Xkdbvxnt46UC4Fxf6JpLxFO', 'doctor', 'CS', 2),
-- secretaria / secretaria123
('Lucía Mendoza', 'secretaria', '[email protected]', '$2b$10$Zs8LX/f2nidTFspYT6QQEOvYa9VI/CuAAmBWg10rcR1BdoP5G/BtO', 'secretary', 'LM', 1);

Doctors

Extended information for medical staff:
ColumnTypeConstraintsDescription
idINTPRIMARY KEY, AUTO_INCREMENTUnique identifier
user_idINTUNIQUE, NOT NULL, FOREIGN KEY → users(id)Associated user account
specialtyVARCHAR(100)Medical specialty
license_numberVARCHAR(50)Professional license number

Doctor_Branches

Many-to-many relationship between doctors and branches:
ColumnTypeConstraintsDescription
doctor_idINTPRIMARY KEY, FOREIGN KEY → doctors(id)Doctor reference
branch_idINTPRIMARY KEY, FOREIGN KEY → branches(id)Branch reference
Example:
INSERT INTO doctor_branches (doctor_id, branch_id) VALUES 
(1, 1), -- Doctor 1 works at Branch 1
(1, 2); -- Doctor 1 also works at Branch 2

Patients

Patient records:
ColumnTypeConstraintsDescription
idINTPRIMARY KEY, AUTO_INCREMENTUnique identifier
medrecnoINTNOT NULLMedical record number
first_nameVARCHAR(100)NOT NULLFirst name
last_nameVARCHAR(100)NOT NULLLast name
cedulaVARCHAR(20)UNIQUENational ID number
birth_dateDATEDate of birth
phoneVARCHAR(20)Contact phone
emailVARCHAR(100)Email address
sexENUM’masculino’, ‘femenino’, ‘otro’Gender
blood_groupVARCHAR(5)Blood type
allergiesTEXTKnown allergies
branch_idINTFOREIGN KEY → branches(id)Primary branch
statusENUM’active’, ‘inactive’, ‘on_hold’Patient status
uuidCHAR(36)UNIQUEUUID for public URLs
created_atTIMESTAMPDEFAULT CURRENT_TIMESTAMPCreation timestamp

Appointments (Citas)

Appointment scheduling:
ColumnTypeConstraintsDescription
idINTPRIMARY KEY, AUTO_INCREMENTUnique identifier
patient_idINTNOT NULL, FOREIGN KEY → patients(id)Patient reference
doctor_idINTNOT NULL, FOREIGN KEY → doctors(id)Doctor reference
branch_idINTNOT NULL, FOREIGN KEY → branches(id)Branch location
appointment_dateDATENOT NULLAppointment date
appointment_timeTIMENOT NULLAppointment time
duration_minutesINTDEFAULT 30Duration in minutes
statusENUM’scheduled’, ‘confirmed’, ‘waiting’, ‘completed’, ‘cancelled’Appointment status
notesTEXTAdditional notes
uuidCHAR(36)UNIQUEUUID for reminders
created_atTIMESTAMPDEFAULT CURRENT_TIMESTAMPCreation timestamp

Medical_Records

Clinical history and consultations:
ColumnTypeConstraintsDescription
idINTPRIMARY KEY, AUTO_INCREMENTUnique identifier
patient_idINTNOT NULL, FOREIGN KEY → patients(id)Patient reference
doctor_idINTNOT NULL, FOREIGN KEY → doctors(id)Attending doctor
record_dateTIMESTAMPDEFAULT CURRENT_TIMESTAMPConsultation date
motifTEXTChief complaint
diagnosisTEXTMedical diagnosis
treatmentTEXTTreatment provided
future_planTEXTFuture treatment plan
next_visitDATENext appointment date
observationsTEXTAdditional observations

Indications

Medical prescriptions and instructions:
ColumnTypeConstraintsDescription
idINTPRIMARY KEY, AUTO_INCREMENTUnique identifier
patient_idINTNOT NULL, FOREIGN KEY → patients(id)Patient reference
doctor_idINTNOT NULL, FOREIGN KEY → doctors(id)Prescribing doctor
indication_dateTIMESTAMPDEFAULT CURRENT_TIMESTAMPDate prescribed
descriptionTEXTNOT NULLPrescription details

Odontograms

Dental chart/diagram data:
ColumnTypeConstraintsDescription
idINTPRIMARY KEY, AUTO_INCREMENTUnique identifier
patient_idINTNOT NULL, FOREIGN KEY → patients(id)Patient reference
tooth_numberINTNOT NULLTooth number (FDI notation)
stateENUM’healthy’, ‘caries’, ‘restored’, ‘extracted’, ‘crown’, ‘treatment’Tooth condition
notesTEXTAdditional notes
updated_atTIMESTAMPDEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMPLast update
Tooth numbering: Uses FDI World Dental Federation notation (11-18, 21-28, 31-38, 41-48)

Patient_Files

File attachments (X-rays, documents, etc.):
ColumnTypeConstraintsDescription
idINTPRIMARY KEY, AUTO_INCREMENTUnique identifier
patient_idINTNOT NULL, FOREIGN KEY → patients(id)Patient reference
nameVARCHAR(255)NOT NULLFile name
file_typeVARCHAR(50)MIME type
file_sizeINTSize in bytes
file_urlVARCHAR(255)NOT NULLStorage URL/path
upload_dateTIMESTAMPDEFAULT CURRENT_TIMESTAMPUpload timestamp

Logs

System audit trail:
ColumnTypeConstraintsDescription
idINTPRIMARY KEY, AUTO_INCREMENTUnique identifier
user_idINTFOREIGN KEY → users(id)User who performed action
actionVARCHAR(50)NOT NULLAction type
moduleVARCHAR(50)NOT NULLModule/feature
descriptionTEXTDetailed description
ip_addressVARCHAR(45)User IP address
created_atTIMESTAMPDEFAULT CURRENT_TIMESTAMPAction timestamp

Permission System Tables

Permissions

Catalog of all system permissions:
ColumnTypeConstraintsDescription
idINTPRIMARY KEY, AUTO_INCREMENTUnique identifier
codeVARCHAR(60)UNIQUE, NOT NULLPermission code (e.g., VIEW_PATIENTS)
labelVARCHAR(100)NOT NULLHuman-readable name
moduleVARCHAR(60)NOT NULLModule grouping
descriptionTEXTDetailed description
sort_orderINTDEFAULT 0Display order
created_atTIMESTAMPDEFAULT CURRENT_TIMESTAMPCreation timestamp
Example permissions:
INSERT INTO permissions (code, label, module, description, sort_order) VALUES
('VIEW_PATIENTS', 'Ver Pacientes', 'Pacientes', 'Acceder al listado de pacientes', 10),
('CREATE_PATIENTS', 'Crear Pacientes', 'Pacientes', 'Registrar un nuevo paciente', 11),
('EDIT_PATIENTS', 'Editar Pacientes', 'Pacientes', 'Modificar datos de un paciente', 12);

User_Permissions

Individual permission assignments:
ColumnTypeConstraintsDescription
idINTPRIMARY KEY, AUTO_INCREMENTUnique identifier
user_idINTNOT NULL, FOREIGN KEY → users(id)User reference
permission_idINTNOT NULL, FOREIGN KEY → permissions(id)Permission reference
grantedTINYINT(1)DEFAULT 11 = granted, 0 = revoked
granted_byINTFOREIGN KEY → users(id)Admin who granted permission
granted_atTIMESTAMPDEFAULT CURRENT_TIMESTAMPGrant timestamp
Unique constraint: (user_id, permission_id)

Stored Procedures

Patient Procedures

Defined in database.sql and referenced in API endpoints: sp_list_patients(search_query)
  • Lists patients with optional search filter
  • Usage: CALL sp_list_patients('') or CALL sp_list_patients('John')
sp_create_patient(…)
  • Creates new patient record
  • Returns: patient_id
  • Usage in API: (src/routes/api/patients/+server.js:69)
const [rows] = await pool.query(
  "CALL sp_create_patient(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
  [first_name, last_name, cedula, birth_date, phone, email, sex, 
   blood_group, allergies, branch_id, patientUuid, locals.user.id]
);
sp_delete_patient(patient_id, user_id)
  • Soft delete or archive patient
  • Logs action in audit trail

Finance Procedures

Defined in sp_finance.sql: sp_get_patient_finance(patient_id)
  • Returns multiple result sets:
    1. Budgets with doctor names
    2. Budget items with service names
    3. Payment history
sp_create_budget(patient_id, doctor_id, total_amount, discount, notes, items_json)
  • Creates budget with line items
  • Uses JSON parameter for items
  • Transactional (rollback on error)
  • Returns: budget_id
sp_register_payment(patient_id, budget_id, amount, payment_method, notes)
  • Records payment transaction
  • Returns: payment_id
sp_update_budget_status(budget_id, status)
  • Updates budget status (draft, approved, rejected)

Permission Procedures

Defined in migration_phase6_permissions.sql: sp_check_single_permission(user_id, permission_code)
  • Checks if user has specific permission
  • Returns: granted (0 or 1)
  • Used in: (src/lib/server/checkPermission.js:25)
sp_get_user_permissions_list(user_id)
  • Returns array of permission codes for user
  • Used for frontend permission checks
sp_get_all_permissions()
  • Returns complete permission catalog
  • Used for admin permission management
sp_upsert_user_permission(user_id, permission_code, granted, granted_by)
  • Assigns or revokes permission
  • Idempotent (insert or update)

Database Relationships

Branches
  ├── Users (branch_id)
  ├── Patients (branch_id)
  ├── Appointments (branch_id)
  └── Doctor_Branches (branch_id)

Users
  ├── Doctors (user_id) [1:1]
  ├── Medical_Records (doctor_id)
  ├── Indications (doctor_id)
  ├── Logs (user_id)
  └── User_Permissions (user_id)

Patients
  ├── Appointments (patient_id)
  ├── Medical_Records (patient_id)
  ├── Indications (patient_id)
  ├── Odontograms (patient_id)
  └── Patient_Files (patient_id)

Doctors
  ├── Appointments (doctor_id)
  ├── Medical_Records (doctor_id)
  └── Doctor_Branches (doctor_id) [Many-to-Many]

Permissions
  └── User_Permissions (permission_id)

Query Patterns

Using the Connection Pool

import { pool } from "$lib/server/db";

// Simple query
const [rows] = await pool.query("SELECT * FROM patients WHERE id = ?", [patientId]);

// Stored procedure
const [results] = await pool.query("CALL sp_list_patients(?)", [searchQuery]);
const patients = results[0]; // First result set

// Transaction
const connection = await pool.getConnection();
try {
  await connection.beginTransaction();
  await connection.query("INSERT INTO ...", [...]);
  await connection.query("UPDATE ...", [...]);
  await connection.commit();
} catch (error) {
  await connection.rollback();
  throw error;
} finally {
  connection.release();
}

Prepared Statements

All queries use prepared statements to prevent SQL injection:
// ✅ SAFE - Parameterized query
await pool.query("SELECT * FROM users WHERE username = ?", [username]);

// ❌ UNSAFE - String concatenation
await pool.query(`SELECT * FROM users WHERE username = '${username}'`);

Database Initialization

  1. Create database: CREATE DATABASE IF NOT EXISTS ontology_db;
  2. Run schema: mysql -u user -p ontology_db < database.sql
  3. Run migrations: mysql -u user -p ontology_db < migration_phase6_permissions.sql
  4. Run stored procedures: mysql -u user -p ontology_db < sp_finance.sql
See Installation Guide for complete setup instructions.

Build docs developers (and LLMs) love