erp-clinicas-database-v2/schemas/01-clinica-core-schema-ddl.sql
rckrdmrd cf07a84e26 Migración desde erp-clinicas/database - Estándar multi-repo v2
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-16 08:12:00 -06:00

447 lines
18 KiB
SQL

-- ============================================================================
-- 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
-- ============================================================================