-- ============================================================================ -- CLINICA CORE SCHEMA - ERP Clinicas -- Tablas principales del sistema clinico -- ============================================================================ -- Fecha: 2026-01-13 -- Version: 1.0 -- Modulos: CL-002 (Pacientes), CL-003 (Citas), CL-004 (Consultas) -- ============================================================================ -- Crear schema si no existe CREATE SCHEMA IF NOT EXISTS clinica; -- ============================================================================ -- ENUMS -- ============================================================================ CREATE TYPE clinica.patient_status AS ENUM ( 'active', 'inactive', 'deceased' ); CREATE TYPE clinica.gender AS ENUM ( 'male', 'female', 'other', 'unknown' ); CREATE TYPE clinica.blood_type AS ENUM ( 'A+', 'A-', 'B+', 'B-', 'AB+', 'AB-', 'O+', 'O-', 'unknown' ); CREATE TYPE clinica.appointment_status AS ENUM ( 'scheduled', 'confirmed', 'in_progress', 'completed', 'cancelled', 'no_show' ); CREATE TYPE clinica.consultation_status AS ENUM ( 'in_progress', 'completed', 'cancelled' ); CREATE TYPE clinica.prescription_status AS ENUM ( 'active', 'completed', 'cancelled' ); -- ============================================================================ -- TABLAS: ESPECIALIDADES Y MEDICOS (CL-002) -- ============================================================================ -- Catalogo de especialidades medicas CREATE TABLE IF NOT EXISTS clinica.specialties ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL, code VARCHAR(20) NOT NULL, name VARCHAR(100) NOT NULL, description TEXT, requires_referral BOOLEAN DEFAULT false, consultation_duration_minutes INTEGER DEFAULT 30, active BOOLEAN DEFAULT true, created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW(), CONSTRAINT uq_specialty_code UNIQUE (tenant_id, code) ); COMMENT ON TABLE clinica.specialties IS 'Catalogo de especialidades medicas - CL-002'; -- Medicos/Doctores CREATE TABLE IF NOT EXISTS clinica.doctors ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL, employee_id UUID, -- FK a hr.employees opcional -- Datos personales first_name VARCHAR(100) NOT NULL, last_name VARCHAR(100) NOT NULL, email VARCHAR(255), phone VARCHAR(20), -- Datos profesionales professional_license VARCHAR(50) NOT NULL, -- Cedula profesional specialty_license VARCHAR(50), -- Cedula de especialidad specialty_id UUID NOT NULL, -- Configuracion consultation_duration_minutes INTEGER DEFAULT 30, max_appointments_per_day INTEGER DEFAULT 20, accepts_insurance BOOLEAN DEFAULT true, -- Control active BOOLEAN DEFAULT true, created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW(), deleted_at TIMESTAMPTZ, CONSTRAINT fk_doctor_specialty FOREIGN KEY (specialty_id) REFERENCES clinica.specialties(id) ); COMMENT ON TABLE clinica.doctors IS 'Registro de medicos y especialistas - CL-002'; COMMENT ON COLUMN clinica.doctors.professional_license IS 'Cedula profesional obligatoria'; -- ============================================================================ -- TABLAS: PACIENTES (CL-002) -- ============================================================================ -- Pacientes CREATE TABLE IF NOT EXISTS clinica.patients ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL, partner_id UUID, -- FK a core.partners opcional (para facturacion) -- Datos personales first_name VARCHAR(100) NOT NULL, last_name VARCHAR(100) NOT NULL, date_of_birth DATE, gender clinica.gender DEFAULT 'unknown', -- Identificacion curp VARCHAR(18), rfc VARCHAR(13), -- Contacto email VARCHAR(255), phone VARCHAR(20), mobile VARCHAR(20), address JSONB, -- {street, city, state, zip, country} -- Datos medicos basicos blood_type clinica.blood_type DEFAULT 'unknown', allergies TEXT[], chronic_conditions TEXT[], emergency_contact_name VARCHAR(200), emergency_contact_phone VARCHAR(20), -- Seguro medico has_insurance BOOLEAN DEFAULT false, insurance_provider VARCHAR(100), insurance_policy_number VARCHAR(50), -- Control status clinica.patient_status DEFAULT 'active', created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW(), deleted_at TIMESTAMPTZ ); COMMENT ON TABLE clinica.patients IS 'Registro de pacientes - CL-002'; COMMENT ON COLUMN clinica.patients.allergies IS 'Lista de alergias conocidas'; COMMENT ON COLUMN clinica.patients.chronic_conditions IS 'Condiciones cronicas conocidas'; -- ============================================================================ -- TABLAS: CITAS (CL-003) -- ============================================================================ -- Horarios disponibles por medico CREATE TABLE IF NOT EXISTS clinica.appointment_slots ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL, doctor_id UUID NOT NULL, -- Horario day_of_week INTEGER NOT NULL CHECK (day_of_week BETWEEN 0 AND 6), -- 0=domingo start_time TIME NOT NULL, end_time TIME NOT NULL, slot_duration_minutes INTEGER DEFAULT 30, -- Control active BOOLEAN DEFAULT true, valid_from DATE DEFAULT CURRENT_DATE, valid_until DATE, created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW(), CONSTRAINT fk_slot_doctor FOREIGN KEY (doctor_id) REFERENCES clinica.doctors(id) ON DELETE CASCADE, CONSTRAINT chk_valid_time_range CHECK (end_time > start_time) ); COMMENT ON TABLE clinica.appointment_slots IS 'Horarios disponibles de medicos - CL-003'; -- Citas medicas CREATE TABLE IF NOT EXISTS clinica.appointments ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL, -- Relaciones patient_id UUID NOT NULL, doctor_id UUID NOT NULL, -- Programacion scheduled_date DATE NOT NULL, scheduled_time TIME NOT NULL, duration_minutes INTEGER DEFAULT 30, -- Estado status clinica.appointment_status DEFAULT 'scheduled', -- Detalles reason TEXT, notes TEXT, -- Confirmacion confirmed_at TIMESTAMPTZ, confirmed_by UUID, -- Cancelacion cancelled_at TIMESTAMPTZ, cancelled_by UUID, cancellation_reason TEXT, -- Cita pasada check_in_at TIMESTAMPTZ, check_out_at TIMESTAMPTZ, -- Control created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW(), created_by UUID, CONSTRAINT fk_appointment_patient FOREIGN KEY (patient_id) REFERENCES clinica.patients(id), CONSTRAINT fk_appointment_doctor FOREIGN KEY (doctor_id) REFERENCES clinica.doctors(id) ); COMMENT ON TABLE clinica.appointments IS 'Citas medicas programadas - CL-003'; -- ============================================================================ -- TABLAS: CONSULTAS Y EXPEDIENTE (CL-004) -- ============================================================================ -- Consultas medicas CREATE TABLE IF NOT EXISTS clinica.consultations ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL, -- Relaciones patient_id UUID NOT NULL, doctor_id UUID NOT NULL, appointment_id UUID, -- puede ser consulta sin cita -- Tiempo started_at TIMESTAMPTZ DEFAULT NOW(), ended_at TIMESTAMPTZ, -- Contenido clinico chief_complaint TEXT, -- Motivo de consulta present_illness TEXT, -- Padecimiento actual physical_examination TEXT, -- Exploracion fisica assessment TEXT, -- Valoracion/Impresion diagnostica plan TEXT, -- Plan de tratamiento -- Signos vitales (snapshot) vital_signs JSONB, -- {weight_kg, height_cm, temperature, blood_pressure, heart_rate, respiratory_rate, oxygen_saturation} -- Estado status clinica.consultation_status DEFAULT 'in_progress', -- Control created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW(), CONSTRAINT fk_consultation_patient FOREIGN KEY (patient_id) REFERENCES clinica.patients(id), CONSTRAINT fk_consultation_doctor FOREIGN KEY (doctor_id) REFERENCES clinica.doctors(id), CONSTRAINT fk_consultation_appointment FOREIGN KEY (appointment_id) REFERENCES clinica.appointments(id) ); COMMENT ON TABLE clinica.consultations IS 'Consultas medicas - CL-004'; COMMENT ON COLUMN clinica.consultations.chief_complaint IS 'Motivo de consulta principal'; COMMENT ON COLUMN clinica.consultations.vital_signs IS 'Signos vitales en formato JSON'; -- Diagnosticos (CIE-10) CREATE TABLE IF NOT EXISTS clinica.diagnoses ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL, consultation_id UUID NOT NULL, -- Diagnostico icd10_code VARCHAR(10), -- Codigo CIE-10 description TEXT NOT NULL, is_primary BOOLEAN DEFAULT false, diagnosis_type VARCHAR(20) DEFAULT 'definitive', -- 'presumptive', 'definitive', 'differential' -- Control created_at TIMESTAMPTZ DEFAULT NOW(), CONSTRAINT fk_diagnosis_consultation FOREIGN KEY (consultation_id) REFERENCES clinica.consultations(id) ON DELETE CASCADE ); COMMENT ON TABLE clinica.diagnoses IS 'Diagnosticos con codificacion CIE-10 - CL-004'; -- Recetas/Prescripciones CREATE TABLE IF NOT EXISTS clinica.prescriptions ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL, consultation_id UUID NOT NULL, patient_id UUID NOT NULL, doctor_id UUID NOT NULL, -- Datos de la receta prescription_number VARCHAR(50), prescription_date DATE DEFAULT CURRENT_DATE, -- Estado status clinica.prescription_status DEFAULT 'active', -- Notas instructions TEXT, notes TEXT, -- Control created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW(), CONSTRAINT fk_prescription_consultation FOREIGN KEY (consultation_id) REFERENCES clinica.consultations(id), CONSTRAINT fk_prescription_patient FOREIGN KEY (patient_id) REFERENCES clinica.patients(id), CONSTRAINT fk_prescription_doctor FOREIGN KEY (doctor_id) REFERENCES clinica.doctors(id) ); COMMENT ON TABLE clinica.prescriptions IS 'Recetas medicas - CL-004'; -- Items de receta (medicamentos) CREATE TABLE IF NOT EXISTS clinica.prescription_items ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL, prescription_id UUID NOT NULL, -- Medicamento medication_name VARCHAR(200) NOT NULL, medication_code VARCHAR(50), -- codigo interno o externo -- Dosificacion dosage VARCHAR(100) NOT NULL, -- "500mg" frequency VARCHAR(100) NOT NULL, -- "cada 8 horas" duration VARCHAR(100), -- "7 dias" quantity INTEGER, -- Instrucciones instructions TEXT, -- Control sequence INTEGER DEFAULT 1, created_at TIMESTAMPTZ DEFAULT NOW(), CONSTRAINT fk_prescription_item FOREIGN KEY (prescription_id) REFERENCES clinica.prescriptions(id) ON DELETE CASCADE ); COMMENT ON TABLE clinica.prescription_items IS 'Medicamentos en receta - CL-004'; -- Signos vitales (historial) CREATE TABLE IF NOT EXISTS clinica.vital_signs ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL, patient_id UUID NOT NULL, consultation_id UUID, recorded_by UUID, -- Mediciones weight_kg NUMERIC(5,2), height_cm NUMERIC(5,2), temperature_celsius NUMERIC(4,1), blood_pressure_systolic INTEGER, blood_pressure_diastolic INTEGER, heart_rate INTEGER, -- latidos por minuto respiratory_rate INTEGER, -- respiraciones por minuto oxygen_saturation INTEGER, -- porcentaje -- Extras glucose_mg_dl NUMERIC(5,1), notes TEXT, -- Control recorded_at TIMESTAMPTZ DEFAULT NOW(), CONSTRAINT fk_vitals_patient FOREIGN KEY (patient_id) REFERENCES clinica.patients(id), CONSTRAINT fk_vitals_consultation FOREIGN KEY (consultation_id) REFERENCES clinica.consultations(id) ); COMMENT ON TABLE clinica.vital_signs IS 'Historial de signos vitales - CL-004'; -- ============================================================================ -- INDICES -- ============================================================================ -- Specialties CREATE INDEX IF NOT EXISTS idx_specialties_tenant ON clinica.specialties(tenant_id); CREATE INDEX IF NOT EXISTS idx_specialties_active ON clinica.specialties(tenant_id, active); -- Doctors CREATE INDEX IF NOT EXISTS idx_doctors_tenant ON clinica.doctors(tenant_id); CREATE INDEX IF NOT EXISTS idx_doctors_specialty ON clinica.doctors(specialty_id); CREATE INDEX IF NOT EXISTS idx_doctors_license ON clinica.doctors(professional_license); CREATE INDEX IF NOT EXISTS idx_doctors_active ON clinica.doctors(tenant_id, active) WHERE deleted_at IS NULL; -- Patients CREATE INDEX IF NOT EXISTS idx_patients_tenant ON clinica.patients(tenant_id); CREATE INDEX IF NOT EXISTS idx_patients_name ON clinica.patients(tenant_id, last_name, first_name); CREATE INDEX IF NOT EXISTS idx_patients_curp ON clinica.patients(curp) WHERE curp IS NOT NULL; CREATE INDEX IF NOT EXISTS idx_patients_phone ON clinica.patients(tenant_id, mobile); CREATE INDEX IF NOT EXISTS idx_patients_status ON clinica.patients(tenant_id, status) WHERE deleted_at IS NULL; -- Appointment Slots CREATE INDEX IF NOT EXISTS idx_slots_tenant ON clinica.appointment_slots(tenant_id); CREATE INDEX IF NOT EXISTS idx_slots_doctor ON clinica.appointment_slots(doctor_id); CREATE INDEX IF NOT EXISTS idx_slots_day ON clinica.appointment_slots(tenant_id, day_of_week, active); -- Appointments CREATE INDEX IF NOT EXISTS idx_appointments_tenant ON clinica.appointments(tenant_id); CREATE INDEX IF NOT EXISTS idx_appointments_patient ON clinica.appointments(patient_id); CREATE INDEX IF NOT EXISTS idx_appointments_doctor ON clinica.appointments(doctor_id); CREATE INDEX IF NOT EXISTS idx_appointments_date ON clinica.appointments(tenant_id, scheduled_date); CREATE INDEX IF NOT EXISTS idx_appointments_status ON clinica.appointments(tenant_id, status); CREATE INDEX IF NOT EXISTS idx_appointments_doctor_date ON clinica.appointments(doctor_id, scheduled_date); -- Consultations CREATE INDEX IF NOT EXISTS idx_consultations_tenant ON clinica.consultations(tenant_id); CREATE INDEX IF NOT EXISTS idx_consultations_patient ON clinica.consultations(patient_id); CREATE INDEX IF NOT EXISTS idx_consultations_doctor ON clinica.consultations(doctor_id); CREATE INDEX IF NOT EXISTS idx_consultations_date ON clinica.consultations(tenant_id, started_at); -- Diagnoses CREATE INDEX IF NOT EXISTS idx_diagnoses_tenant ON clinica.diagnoses(tenant_id); CREATE INDEX IF NOT EXISTS idx_diagnoses_consultation ON clinica.diagnoses(consultation_id); CREATE INDEX IF NOT EXISTS idx_diagnoses_icd10 ON clinica.diagnoses(icd10_code) WHERE icd10_code IS NOT NULL; -- Prescriptions CREATE INDEX IF NOT EXISTS idx_prescriptions_tenant ON clinica.prescriptions(tenant_id); CREATE INDEX IF NOT EXISTS idx_prescriptions_consultation ON clinica.prescriptions(consultation_id); CREATE INDEX IF NOT EXISTS idx_prescriptions_patient ON clinica.prescriptions(patient_id); CREATE INDEX IF NOT EXISTS idx_prescriptions_date ON clinica.prescriptions(tenant_id, prescription_date); -- Prescription Items CREATE INDEX IF NOT EXISTS idx_prescription_items_prescription ON clinica.prescription_items(prescription_id); -- Vital Signs CREATE INDEX IF NOT EXISTS idx_vitals_tenant ON clinica.vital_signs(tenant_id); CREATE INDEX IF NOT EXISTS idx_vitals_patient ON clinica.vital_signs(patient_id); CREATE INDEX IF NOT EXISTS idx_vitals_date ON clinica.vital_signs(patient_id, recorded_at DESC); -- ============================================================================ -- ROW LEVEL SECURITY -- ============================================================================ ALTER TABLE clinica.specialties ENABLE ROW LEVEL SECURITY; ALTER TABLE clinica.doctors ENABLE ROW LEVEL SECURITY; ALTER TABLE clinica.patients ENABLE ROW LEVEL SECURITY; ALTER TABLE clinica.appointment_slots ENABLE ROW LEVEL SECURITY; ALTER TABLE clinica.appointments ENABLE ROW LEVEL SECURITY; ALTER TABLE clinica.consultations ENABLE ROW LEVEL SECURITY; ALTER TABLE clinica.diagnoses ENABLE ROW LEVEL SECURITY; ALTER TABLE clinica.prescriptions ENABLE ROW LEVEL SECURITY; ALTER TABLE clinica.prescription_items ENABLE ROW LEVEL SECURITY; ALTER TABLE clinica.vital_signs ENABLE ROW LEVEL SECURITY; -- Politicas de aislamiento por tenant CREATE POLICY tenant_isolation_specialties ON clinica.specialties USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID); CREATE POLICY tenant_isolation_doctors ON clinica.doctors USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID); CREATE POLICY tenant_isolation_patients ON clinica.patients USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID); CREATE POLICY tenant_isolation_slots ON clinica.appointment_slots USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID); CREATE POLICY tenant_isolation_appointments ON clinica.appointments USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID); CREATE POLICY tenant_isolation_consultations ON clinica.consultations USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID); CREATE POLICY tenant_isolation_diagnoses ON clinica.diagnoses USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID); CREATE POLICY tenant_isolation_prescriptions ON clinica.prescriptions USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID); CREATE POLICY tenant_isolation_prescription_items ON clinica.prescription_items USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID); CREATE POLICY tenant_isolation_vitals ON clinica.vital_signs USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID); -- ============================================================================ -- FIN CLINICA CORE SCHEMA -- ============================================================================