diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..c9a2891 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,11 @@ +[submodule "backend"] + path = backend + url = git@gitea-server:rckrdmrd/clinica-veterinaria-backend-v2.git + +[submodule "database"] + path = database + url = git@gitea-server:rckrdmrd/clinica-veterinaria-database-v2.git + +[submodule "frontend"] + path = frontend + url = git@gitea-server:rckrdmrd/clinica-veterinaria-frontend-v2.git diff --git a/backend b/backend new file mode 160000 index 0000000..25d59cd --- /dev/null +++ b/backend @@ -0,0 +1 @@ +Subproject commit 25d59cd031f9577af1a3e3fd7870887ca6db74be diff --git a/database b/database new file mode 160000 index 0000000..afa4f1a --- /dev/null +++ b/database @@ -0,0 +1 @@ +Subproject commit afa4f1a6fcb4a12c8e1eed7112972a5323cdad6e diff --git a/database/schemas/01-veterinaria-schema-ddl.sql b/database/schemas/01-veterinaria-schema-ddl.sql deleted file mode 100644 index 5959b93..0000000 --- a/database/schemas/01-veterinaria-schema-ddl.sql +++ /dev/null @@ -1,387 +0,0 @@ --- ============================================================================ --- VETERINARIA SCHEMA - Especialización de ERP-Clínicas --- Clínica Veterinaria --- ============================================================================ --- Fecha: 2026-01-04 --- Versión: 1.0 --- Hereda de: erp-clinicas FASE-8 --- ============================================================================ - --- Schema -CREATE SCHEMA IF NOT EXISTS veterinaria; - --- ============================================================================ --- ENUMS --- ============================================================================ - -DO $$ BEGIN - CREATE TYPE veterinaria.sexo_animal AS ENUM ('macho', 'hembra', 'desconocido'); -EXCEPTION WHEN duplicate_object THEN NULL; -END $$; - -DO $$ BEGIN - CREATE TYPE veterinaria.estado_hospitalizacion AS ENUM ( - 'ingresado', 'en_tratamiento', 'estable', 'critico', 'alta', 'fallecido' - ); -EXCEPTION WHEN duplicate_object THEN NULL; -END $$; - --- ============================================================================ --- CATÁLOGOS --- ============================================================================ - --- Especies -CREATE TABLE IF NOT EXISTS veterinaria.especies ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - tenant_id UUID NOT NULL, - nombre VARCHAR(50) NOT NULL, - nombre_cientifico VARCHAR(100), - descripcion TEXT, - active BOOLEAN DEFAULT true, - created_at TIMESTAMPTZ DEFAULT NOW() -); - -COMMENT ON TABLE veterinaria.especies IS 'Catálogo de especies animales'; - --- Razas -CREATE TABLE IF NOT EXISTS veterinaria.razas ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - tenant_id UUID NOT NULL, - especie_id UUID NOT NULL REFERENCES veterinaria.especies(id) ON DELETE CASCADE, - nombre VARCHAR(100) NOT NULL, - descripcion TEXT, - tamanio_promedio VARCHAR(20), -- 'pequeño', 'mediano', 'grande', 'gigante' - peso_promedio_kg NUMERIC(5,2), - active BOOLEAN DEFAULT true, - created_at TIMESTAMPTZ DEFAULT NOW() -); - -COMMENT ON TABLE veterinaria.razas IS 'Catálogo de razas por especie'; - --- Vacunas -CREATE TABLE IF NOT EXISTS veterinaria.vacunas ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - tenant_id UUID NOT NULL, - nombre VARCHAR(100) NOT NULL, - descripcion TEXT, - especie_id UUID REFERENCES veterinaria.especies(id), - laboratorio VARCHAR(100), - dosis_ml NUMERIC(5,2), - intervalo_refuerzo_dias INTEGER, - es_obligatoria BOOLEAN DEFAULT false, - active BOOLEAN DEFAULT true, - created_at TIMESTAMPTZ DEFAULT NOW() -); - -COMMENT ON TABLE veterinaria.vacunas IS 'Catálogo de vacunas veterinarias'; - --- ============================================================================ --- TABLAS PRINCIPALES --- ============================================================================ - --- Propietarios (dueños de mascotas) -CREATE TABLE IF NOT EXISTS veterinaria.propietarios ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - tenant_id UUID NOT NULL, - partner_id UUID, -- Referencia opcional a core.partners - nombre VARCHAR(100) NOT NULL, - apellidos VARCHAR(100), - telefono VARCHAR(20), - telefono_emergencia VARCHAR(20), - email VARCHAR(100), - direccion TEXT, - rfc VARCHAR(13), - -- Control - active BOOLEAN DEFAULT true, - created_at TIMESTAMPTZ DEFAULT NOW(), - updated_at TIMESTAMPTZ DEFAULT NOW() -); - -COMMENT ON TABLE veterinaria.propietarios IS 'Propietarios/dueños de mascotas'; - --- Mascotas (pacientes) -CREATE TABLE IF NOT EXISTS veterinaria.mascotas ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - tenant_id UUID NOT NULL, - propietario_id UUID NOT NULL REFERENCES veterinaria.propietarios(id), - especie_id UUID NOT NULL REFERENCES veterinaria.especies(id), - raza_id UUID REFERENCES veterinaria.razas(id), - -- Datos básicos - nombre VARCHAR(100) NOT NULL, - sexo veterinaria.sexo_animal DEFAULT 'desconocido', - fecha_nacimiento DATE, - edad_aproximada VARCHAR(50), -- "3 años", "6 meses" - color VARCHAR(50), - peso_kg NUMERIC(6,2), - -- Identificación - numero_chip VARCHAR(50), - tiene_chip BOOLEAN DEFAULT false, - -- Estado - esterilizado BOOLEAN DEFAULT false, - fecha_esterilizacion DATE, - -- Notas - alergias TEXT, - condiciones_especiales TEXT, - notas TEXT, - -- Foto - foto_url VARCHAR(255), - -- Control - active BOOLEAN DEFAULT true, - created_at TIMESTAMPTZ DEFAULT NOW(), - updated_at TIMESTAMPTZ DEFAULT NOW() -); - -COMMENT ON TABLE veterinaria.mascotas IS 'Mascotas/pacientes de la clínica veterinaria'; - --- Cartilla de vacunación -CREATE TABLE IF NOT EXISTS veterinaria.cartilla_vacunacion ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - tenant_id UUID NOT NULL, - mascota_id UUID NOT NULL REFERENCES veterinaria.mascotas(id) ON DELETE CASCADE, - vacuna_id UUID NOT NULL REFERENCES veterinaria.vacunas(id), - veterinario_id UUID, -- Referencia a clinica.doctors - -- Datos de aplicación - fecha_aplicacion DATE NOT NULL, - fecha_proximo_refuerzo DATE, - lote VARCHAR(50), - laboratorio VARCHAR(100), - -- Notas - observaciones TEXT, - -- Control - created_at TIMESTAMPTZ DEFAULT NOW() -); - -COMMENT ON TABLE veterinaria.cartilla_vacunacion IS 'Historial de vacunación de mascotas'; - --- Desparasitaciones -CREATE TABLE IF NOT EXISTS veterinaria.desparasitaciones ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - tenant_id UUID NOT NULL, - mascota_id UUID NOT NULL REFERENCES veterinaria.mascotas(id) ON DELETE CASCADE, - veterinario_id UUID, - -- Datos - tipo VARCHAR(50) NOT NULL, -- 'interna', 'externa', 'ambas' - producto VARCHAR(100) NOT NULL, - dosis VARCHAR(50), - via_administracion VARCHAR(50), - fecha_aplicacion DATE NOT NULL, - fecha_proxima DATE, - -- Notas - observaciones TEXT, - -- Control - created_at TIMESTAMPTZ DEFAULT NOW() -); - -COMMENT ON TABLE veterinaria.desparasitaciones IS 'Historial de desparasitaciones'; - --- Hospitalización -CREATE TABLE IF NOT EXISTS veterinaria.hospitalizacion ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - tenant_id UUID NOT NULL, - mascota_id UUID NOT NULL REFERENCES veterinaria.mascotas(id), - veterinario_id UUID, - consultation_id UUID, -- Referencia a clinica.consultations - -- Datos de ingreso - fecha_ingreso TIMESTAMPTZ NOT NULL DEFAULT NOW(), - motivo_ingreso TEXT NOT NULL, - diagnostico_ingreso TEXT, - -- Ubicación - area VARCHAR(50), -- 'jaula_pequena', 'jaula_grande', 'quirofano', 'uci' - numero_jaula VARCHAR(20), - -- Estado - estado veterinaria.estado_hospitalizacion DEFAULT 'ingresado', - -- Alta - fecha_alta TIMESTAMPTZ, - diagnostico_alta TEXT, - instrucciones_alta TEXT, - -- Control - created_at TIMESTAMPTZ DEFAULT NOW(), - updated_at TIMESTAMPTZ DEFAULT NOW() -); - -COMMENT ON TABLE veterinaria.hospitalizacion IS 'Registro de hospitalizaciones'; - --- Monitoreo de hospitalización -CREATE TABLE IF NOT EXISTS veterinaria.hospitalizacion_monitoreo ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - tenant_id UUID NOT NULL, - hospitalizacion_id UUID NOT NULL REFERENCES veterinaria.hospitalizacion(id) ON DELETE CASCADE, - -- Signos vitales - fecha_hora TIMESTAMPTZ NOT NULL DEFAULT NOW(), - peso_kg NUMERIC(6,2), - temperatura NUMERIC(4,1), - frecuencia_cardiaca INTEGER, - frecuencia_respiratoria INTEGER, - -- Alimentación - comio BOOLEAN, - bebio_agua BOOLEAN, - -- Eliminación - orino BOOLEAN, - defeco BOOLEAN, - consistencia_heces VARCHAR(50), - -- Estado - estado_animo VARCHAR(50), - nivel_dolor INTEGER CHECK (nivel_dolor BETWEEN 0 AND 10), - -- Notas - observaciones TEXT, - registrado_por UUID, -- employee_id - -- Control - created_at TIMESTAMPTZ DEFAULT NOW() -); - -COMMENT ON TABLE veterinaria.hospitalizacion_monitoreo IS 'Monitoreo durante hospitalización'; - --- Servicios de estética -CREATE TABLE IF NOT EXISTS veterinaria.estetica ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - tenant_id UUID NOT NULL, - mascota_id UUID NOT NULL REFERENCES veterinaria.mascotas(id), - estilista_id UUID, -- employee_id - -- Servicios - fecha_servicio TIMESTAMPTZ NOT NULL DEFAULT NOW(), - servicios TEXT[], -- ['baño', 'corte', 'limpieza_oidos', 'corte_unas'] - tipo_corte VARCHAR(50), - shampoo_usado VARCHAR(100), - -- Estado - estado VARCHAR(20) DEFAULT 'pendiente', -- 'pendiente', 'en_proceso', 'terminado' - hora_inicio TIME, - hora_fin TIME, - -- Notas - observaciones TEXT, - observaciones_piel TEXT, - -- Precio - precio NUMERIC(10,2), - -- Control - created_at TIMESTAMPTZ DEFAULT NOW() -); - -COMMENT ON TABLE veterinaria.estetica IS 'Servicios de estética/grooming'; - --- ============================================================================ --- EXTENSIONES A TABLAS DE ERP-CLINICAS --- ============================================================================ - --- Extensión a clinica.consultations (si existe) -DO $$ -BEGIN - IF EXISTS (SELECT 1 FROM information_schema.tables - WHERE table_schema = 'clinica' AND table_name = 'consultations') THEN - - -- mascota_id - IF NOT EXISTS (SELECT 1 FROM information_schema.columns - WHERE table_schema = 'clinica' AND table_name = 'consultations' - AND column_name = 'mascota_id') THEN - ALTER TABLE clinica.consultations ADD COLUMN mascota_id UUID - REFERENCES veterinaria.mascotas(id); - END IF; - - -- peso_actual - IF NOT EXISTS (SELECT 1 FROM information_schema.columns - WHERE table_schema = 'clinica' AND table_name = 'consultations' - AND column_name = 'peso_actual') THEN - ALTER TABLE clinica.consultations ADD COLUMN peso_actual NUMERIC(6,2); - END IF; - - -- temperatura - IF NOT EXISTS (SELECT 1 FROM information_schema.columns - WHERE table_schema = 'clinica' AND table_name = 'consultations' - AND column_name = 'temperatura') THEN - ALTER TABLE clinica.consultations ADD COLUMN temperatura NUMERIC(4,1); - END IF; - - END IF; -END $$; - --- ============================================================================ --- ÍNDICES --- ============================================================================ - -CREATE INDEX IF NOT EXISTS idx_especies_tenant ON veterinaria.especies(tenant_id); -CREATE INDEX IF NOT EXISTS idx_razas_tenant ON veterinaria.razas(tenant_id); -CREATE INDEX IF NOT EXISTS idx_razas_especie ON veterinaria.razas(especie_id); -CREATE INDEX IF NOT EXISTS idx_vacunas_tenant ON veterinaria.vacunas(tenant_id); -CREATE INDEX IF NOT EXISTS idx_vacunas_especie ON veterinaria.vacunas(especie_id); - -CREATE INDEX IF NOT EXISTS idx_propietarios_tenant ON veterinaria.propietarios(tenant_id); -CREATE INDEX IF NOT EXISTS idx_propietarios_telefono ON veterinaria.propietarios(telefono); - -CREATE INDEX IF NOT EXISTS idx_mascotas_tenant ON veterinaria.mascotas(tenant_id); -CREATE INDEX IF NOT EXISTS idx_mascotas_propietario ON veterinaria.mascotas(propietario_id); -CREATE INDEX IF NOT EXISTS idx_mascotas_especie ON veterinaria.mascotas(especie_id); -CREATE INDEX IF NOT EXISTS idx_mascotas_chip ON veterinaria.mascotas(numero_chip) WHERE numero_chip IS NOT NULL; - -CREATE INDEX IF NOT EXISTS idx_cartilla_tenant ON veterinaria.cartilla_vacunacion(tenant_id); -CREATE INDEX IF NOT EXISTS idx_cartilla_mascota ON veterinaria.cartilla_vacunacion(mascota_id); -CREATE INDEX IF NOT EXISTS idx_cartilla_fecha ON veterinaria.cartilla_vacunacion(fecha_proximo_refuerzo); - -CREATE INDEX IF NOT EXISTS idx_desparasitaciones_tenant ON veterinaria.desparasitaciones(tenant_id); -CREATE INDEX IF NOT EXISTS idx_desparasitaciones_mascota ON veterinaria.desparasitaciones(mascota_id); - -CREATE INDEX IF NOT EXISTS idx_hospitalizacion_tenant ON veterinaria.hospitalizacion(tenant_id); -CREATE INDEX IF NOT EXISTS idx_hospitalizacion_mascota ON veterinaria.hospitalizacion(mascota_id); -CREATE INDEX IF NOT EXISTS idx_hospitalizacion_estado ON veterinaria.hospitalizacion(tenant_id, estado); - -CREATE INDEX IF NOT EXISTS idx_hospitalizacion_monitoreo_hosp ON veterinaria.hospitalizacion_monitoreo(hospitalizacion_id); - -CREATE INDEX IF NOT EXISTS idx_estetica_tenant ON veterinaria.estetica(tenant_id); -CREATE INDEX IF NOT EXISTS idx_estetica_mascota ON veterinaria.estetica(mascota_id); -CREATE INDEX IF NOT EXISTS idx_estetica_fecha ON veterinaria.estetica(fecha_servicio); - --- ============================================================================ --- RLS --- ============================================================================ - -ALTER TABLE veterinaria.especies ENABLE ROW LEVEL SECURITY; -ALTER TABLE veterinaria.razas ENABLE ROW LEVEL SECURITY; -ALTER TABLE veterinaria.vacunas ENABLE ROW LEVEL SECURITY; -ALTER TABLE veterinaria.propietarios ENABLE ROW LEVEL SECURITY; -ALTER TABLE veterinaria.mascotas ENABLE ROW LEVEL SECURITY; -ALTER TABLE veterinaria.cartilla_vacunacion ENABLE ROW LEVEL SECURITY; -ALTER TABLE veterinaria.desparasitaciones ENABLE ROW LEVEL SECURITY; -ALTER TABLE veterinaria.hospitalizacion ENABLE ROW LEVEL SECURITY; -ALTER TABLE veterinaria.hospitalizacion_monitoreo ENABLE ROW LEVEL SECURITY; -ALTER TABLE veterinaria.estetica ENABLE ROW LEVEL SECURITY; - -DROP POLICY IF EXISTS tenant_isolation_especies ON veterinaria.especies; -CREATE POLICY tenant_isolation_especies ON veterinaria.especies - USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID); - -DROP POLICY IF EXISTS tenant_isolation_razas ON veterinaria.razas; -CREATE POLICY tenant_isolation_razas ON veterinaria.razas - USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID); - -DROP POLICY IF EXISTS tenant_isolation_vacunas ON veterinaria.vacunas; -CREATE POLICY tenant_isolation_vacunas ON veterinaria.vacunas - USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID); - -DROP POLICY IF EXISTS tenant_isolation_propietarios ON veterinaria.propietarios; -CREATE POLICY tenant_isolation_propietarios ON veterinaria.propietarios - USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID); - -DROP POLICY IF EXISTS tenant_isolation_mascotas ON veterinaria.mascotas; -CREATE POLICY tenant_isolation_mascotas ON veterinaria.mascotas - USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID); - -DROP POLICY IF EXISTS tenant_isolation_cartilla ON veterinaria.cartilla_vacunacion; -CREATE POLICY tenant_isolation_cartilla ON veterinaria.cartilla_vacunacion - USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID); - -DROP POLICY IF EXISTS tenant_isolation_desparasitaciones ON veterinaria.desparasitaciones; -CREATE POLICY tenant_isolation_desparasitaciones ON veterinaria.desparasitaciones - USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID); - -DROP POLICY IF EXISTS tenant_isolation_hospitalizacion ON veterinaria.hospitalizacion; -CREATE POLICY tenant_isolation_hospitalizacion ON veterinaria.hospitalizacion - USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID); - -DROP POLICY IF EXISTS tenant_isolation_hosp_monitoreo ON veterinaria.hospitalizacion_monitoreo; -CREATE POLICY tenant_isolation_hosp_monitoreo ON veterinaria.hospitalizacion_monitoreo - USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID); - -DROP POLICY IF EXISTS tenant_isolation_estetica ON veterinaria.estetica; -CREATE POLICY tenant_isolation_estetica ON veterinaria.estetica - USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID); - --- ============================================================================ --- FIN VETERINARIA SCHEMA --- ============================================================================ diff --git a/database/schemas/02-veterinaria-farmacia-ddl.sql b/database/schemas/02-veterinaria-farmacia-ddl.sql deleted file mode 100644 index 2287e32..0000000 --- a/database/schemas/02-veterinaria-farmacia-ddl.sql +++ /dev/null @@ -1,464 +0,0 @@ --- ============================================================================ --- VETERINARIA SCHEMA - Farmacia (VET-006) --- Sistema de gestion de farmacia veterinaria --- ============================================================================ --- Fecha: 2026-01-07 --- Version: 1.0 --- Basado en: VET-006-farmacia.md --- ============================================================================ - --- ============================================================================ --- ENUMS --- ============================================================================ - -DO $$ BEGIN - CREATE TYPE veterinaria.categoria_medicamento AS ENUM ( - 'antibiotico', - 'antiparasitario', - 'analgesico', - 'antiinflamatorio', - 'vacuna', - 'vitamina', - 'dermatologico', - 'oftalmico', - 'cardiaco', - 'digestivo', - 'otro' - ); -EXCEPTION WHEN duplicate_object THEN NULL; -END $$; - -DO $$ BEGIN - CREATE TYPE veterinaria.tipo_movimiento_farmacia AS ENUM ( - 'entrada', - 'salida', - 'ajuste_positivo', - 'ajuste_negativo', - 'devolucion', - 'merma' - ); -EXCEPTION WHEN duplicate_object THEN NULL; -END $$; - -DO $$ BEGIN - CREATE TYPE veterinaria.fraccion_controlada AS ENUM ( - 'no_controlado', - 'fraccion_i', - 'fraccion_ii', - 'fraccion_iii', - 'fraccion_iv' - ); -EXCEPTION WHEN duplicate_object THEN NULL; -END $$; - --- ============================================================================ --- TABLAS PRINCIPALES --- ============================================================================ - --- Catalogo de medicamentos -CREATE TABLE IF NOT EXISTS veterinaria.medicamentos ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - tenant_id UUID NOT NULL, - -- Identificacion - codigo VARCHAR(50), - nombre VARCHAR(150) NOT NULL, - nombre_comercial VARCHAR(150), - principio_activo VARCHAR(200), - -- Clasificacion - categoria veterinaria.categoria_medicamento DEFAULT 'otro', - -- Presentacion - presentacion VARCHAR(100), -- 'tabletas', 'inyectable', 'suspension', etc. - concentracion VARCHAR(50), -- '500mg', '100mg/ml' - contenido VARCHAR(50), -- '30 tabletas', '100ml' - -- Fabricante - laboratorio VARCHAR(100), - -- Control - requiere_receta BOOLEAN DEFAULT false, - controlado BOOLEAN DEFAULT false, - fraccion_controlada veterinaria.fraccion_controlada DEFAULT 'no_controlado', - -- Stock - stock_minimo INTEGER DEFAULT 10, - stock_actual INTEGER DEFAULT 0, - -- Precios - precio_compra NUMERIC(10,2), - precio_venta NUMERIC(10,2), - -- Especies aplicables (NULL = todas) - especies_aplicables UUID[], -- Array de especie_id - -- Estado - active BOOLEAN DEFAULT true, - created_at TIMESTAMPTZ DEFAULT NOW(), - updated_at TIMESTAMPTZ DEFAULT NOW() -); - -COMMENT ON TABLE veterinaria.medicamentos IS 'Catalogo de medicamentos veterinarios'; - --- Lotes de medicamentos (control de caducidad) -CREATE TABLE IF NOT EXISTS veterinaria.medicamentos_lotes ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - tenant_id UUID NOT NULL, - medicamento_id UUID NOT NULL REFERENCES veterinaria.medicamentos(id) ON DELETE CASCADE, - -- Lote - numero_lote VARCHAR(50) NOT NULL, - fecha_caducidad DATE NOT NULL, - -- Cantidades - cantidad_inicial INTEGER NOT NULL, - cantidad_actual INTEGER NOT NULL, - -- Compra - precio_compra NUMERIC(10,2), - factura_compra VARCHAR(50), - proveedor VARCHAR(100), - fecha_recepcion DATE DEFAULT CURRENT_DATE, - -- Estado - bloqueado BOOLEAN DEFAULT false, -- Bloquear si esta vencido - motivo_bloqueo TEXT, - -- Control - created_at TIMESTAMPTZ DEFAULT NOW(), - updated_at TIMESTAMPTZ DEFAULT NOW() -); - -COMMENT ON TABLE veterinaria.medicamentos_lotes IS 'Lotes de medicamentos con control de caducidad'; - --- Dispensaciones de medicamentos -CREATE TABLE IF NOT EXISTS veterinaria.dispensaciones ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - tenant_id UUID NOT NULL, - -- Referencias - medicamento_id UUID NOT NULL REFERENCES veterinaria.medicamentos(id), - lote_id UUID NOT NULL REFERENCES veterinaria.medicamentos_lotes(id), - mascota_id UUID REFERENCES veterinaria.mascotas(id), - veterinario_id UUID, -- Referencia a clinica.doctors - receta_id UUID, -- Referencia a clinica.prescriptions si existe - consultation_id UUID, -- Referencia a clinica.consultations - -- Dispensacion - cantidad INTEGER NOT NULL, - fecha_dispensacion TIMESTAMPTZ DEFAULT NOW(), - -- Instrucciones - dosis VARCHAR(100), -- '1 tableta cada 8 horas' - duracion_tratamiento VARCHAR(50), -- '7 dias' - instrucciones TEXT, - -- Control - dispensado_por UUID, -- employee_id - notas TEXT, - -- Auditoria - created_at TIMESTAMPTZ DEFAULT NOW() -); - -COMMENT ON TABLE veterinaria.dispensaciones IS 'Registro de dispensacion de medicamentos'; - --- Movimientos de inventario (kardex) -CREATE TABLE IF NOT EXISTS veterinaria.movimientos_farmacia ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - tenant_id UUID NOT NULL, - medicamento_id UUID NOT NULL REFERENCES veterinaria.medicamentos(id), - lote_id UUID REFERENCES veterinaria.medicamentos_lotes(id), - -- Movimiento - tipo veterinaria.tipo_movimiento_farmacia NOT NULL, - cantidad INTEGER NOT NULL, - stock_anterior INTEGER NOT NULL, - stock_posterior INTEGER NOT NULL, - -- Referencia - referencia_tipo VARCHAR(50), -- 'dispensacion', 'compra', 'ajuste' - referencia_id UUID, - -- Detalles - motivo TEXT, - documento VARCHAR(100), -- Factura, nota, etc. - -- Auditoria - usuario_id UUID, - created_at TIMESTAMPTZ DEFAULT NOW() -); - -COMMENT ON TABLE veterinaria.movimientos_farmacia IS 'Kardex de movimientos de inventario de farmacia'; - --- Bitacora de medicamentos controlados -CREATE TABLE IF NOT EXISTS veterinaria.bitacora_controlados ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - tenant_id UUID NOT NULL, - medicamento_id UUID NOT NULL REFERENCES veterinaria.medicamentos(id), - lote_id UUID REFERENCES veterinaria.medicamentos_lotes(id), - dispensacion_id UUID REFERENCES veterinaria.dispensaciones(id), - -- Movimiento - tipo_movimiento veterinaria.tipo_movimiento_farmacia NOT NULL, - cantidad INTEGER NOT NULL, - -- Paciente - mascota_id UUID REFERENCES veterinaria.mascotas(id), - propietario_nombre VARCHAR(200), -- Snapshot del nombre - -- Prescripcion - receta_id UUID, - veterinario_id UUID, - veterinario_cedula VARCHAR(50), - -- Justificacion - justificacion TEXT NOT NULL, - diagnostico TEXT, - -- Auditoria - fecha_registro TIMESTAMPTZ DEFAULT NOW(), - registrado_por UUID NOT NULL, - ip_address VARCHAR(45), - created_at TIMESTAMPTZ DEFAULT NOW() -); - -COMMENT ON TABLE veterinaria.bitacora_controlados IS 'Bitacora de movimientos de medicamentos controlados (requerido por COFEPRIS)'; - --- ============================================================================ --- INDICES --- ============================================================================ - -CREATE INDEX IF NOT EXISTS idx_medicamentos_tenant ON veterinaria.medicamentos(tenant_id); -CREATE INDEX IF NOT EXISTS idx_medicamentos_codigo ON veterinaria.medicamentos(tenant_id, codigo); -CREATE INDEX IF NOT EXISTS idx_medicamentos_nombre ON veterinaria.medicamentos(tenant_id, nombre); -CREATE INDEX IF NOT EXISTS idx_medicamentos_categoria ON veterinaria.medicamentos(tenant_id, categoria); -CREATE INDEX IF NOT EXISTS idx_medicamentos_controlado ON veterinaria.medicamentos(tenant_id, controlado) WHERE controlado = true; -CREATE INDEX IF NOT EXISTS idx_medicamentos_stock_bajo ON veterinaria.medicamentos(tenant_id) - WHERE stock_actual <= stock_minimo; - -CREATE INDEX IF NOT EXISTS idx_lotes_tenant ON veterinaria.medicamentos_lotes(tenant_id); -CREATE INDEX IF NOT EXISTS idx_lotes_medicamento ON veterinaria.medicamentos_lotes(medicamento_id); -CREATE INDEX IF NOT EXISTS idx_lotes_caducidad ON veterinaria.medicamentos_lotes(fecha_caducidad); -CREATE INDEX IF NOT EXISTS idx_lotes_numero ON veterinaria.medicamentos_lotes(tenant_id, numero_lote); -CREATE INDEX IF NOT EXISTS idx_lotes_proximos_caducar ON veterinaria.medicamentos_lotes(tenant_id, fecha_caducidad) - WHERE cantidad_actual > 0 AND bloqueado = false; - -CREATE INDEX IF NOT EXISTS idx_dispensaciones_tenant ON veterinaria.dispensaciones(tenant_id); -CREATE INDEX IF NOT EXISTS idx_dispensaciones_medicamento ON veterinaria.dispensaciones(medicamento_id); -CREATE INDEX IF NOT EXISTS idx_dispensaciones_mascota ON veterinaria.dispensaciones(mascota_id); -CREATE INDEX IF NOT EXISTS idx_dispensaciones_fecha ON veterinaria.dispensaciones(fecha_dispensacion); -CREATE INDEX IF NOT EXISTS idx_dispensaciones_veterinario ON veterinaria.dispensaciones(veterinario_id); - -CREATE INDEX IF NOT EXISTS idx_movimientos_tenant ON veterinaria.movimientos_farmacia(tenant_id); -CREATE INDEX IF NOT EXISTS idx_movimientos_medicamento ON veterinaria.movimientos_farmacia(medicamento_id); -CREATE INDEX IF NOT EXISTS idx_movimientos_fecha ON veterinaria.movimientos_farmacia(created_at); - -CREATE INDEX IF NOT EXISTS idx_bitacora_tenant ON veterinaria.bitacora_controlados(tenant_id); -CREATE INDEX IF NOT EXISTS idx_bitacora_medicamento ON veterinaria.bitacora_controlados(medicamento_id); -CREATE INDEX IF NOT EXISTS idx_bitacora_fecha ON veterinaria.bitacora_controlados(fecha_registro); - --- ============================================================================ --- RLS --- ============================================================================ - -ALTER TABLE veterinaria.medicamentos ENABLE ROW LEVEL SECURITY; -ALTER TABLE veterinaria.medicamentos_lotes ENABLE ROW LEVEL SECURITY; -ALTER TABLE veterinaria.dispensaciones ENABLE ROW LEVEL SECURITY; -ALTER TABLE veterinaria.movimientos_farmacia ENABLE ROW LEVEL SECURITY; -ALTER TABLE veterinaria.bitacora_controlados ENABLE ROW LEVEL SECURITY; - -DROP POLICY IF EXISTS tenant_isolation_medicamentos ON veterinaria.medicamentos; -CREATE POLICY tenant_isolation_medicamentos ON veterinaria.medicamentos - USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID); - -DROP POLICY IF EXISTS tenant_isolation_lotes ON veterinaria.medicamentos_lotes; -CREATE POLICY tenant_isolation_lotes ON veterinaria.medicamentos_lotes - USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID); - -DROP POLICY IF EXISTS tenant_isolation_dispensaciones ON veterinaria.dispensaciones; -CREATE POLICY tenant_isolation_dispensaciones ON veterinaria.dispensaciones - USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID); - -DROP POLICY IF EXISTS tenant_isolation_movimientos ON veterinaria.movimientos_farmacia; -CREATE POLICY tenant_isolation_movimientos ON veterinaria.movimientos_farmacia - USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID); - -DROP POLICY IF EXISTS tenant_isolation_bitacora ON veterinaria.bitacora_controlados; -CREATE POLICY tenant_isolation_bitacora ON veterinaria.bitacora_controlados - USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID); - --- ============================================================================ --- TRIGGERS --- ============================================================================ - --- Trigger para actualizar stock_actual en medicamentos -CREATE OR REPLACE FUNCTION veterinaria.actualizar_stock_medicamento() -RETURNS TRIGGER AS $$ -BEGIN - IF TG_OP = 'INSERT' THEN - -- Actualizar stock del medicamento - UPDATE veterinaria.medicamentos - SET stock_actual = stock_actual + NEW.cantidad_inicial, - updated_at = NOW() - WHERE id = NEW.medicamento_id; - ELSIF TG_OP = 'UPDATE' THEN - -- Si cambia cantidad_actual - UPDATE veterinaria.medicamentos - SET stock_actual = ( - SELECT COALESCE(SUM(cantidad_actual), 0) - FROM veterinaria.medicamentos_lotes - WHERE medicamento_id = NEW.medicamento_id - AND bloqueado = false - ), - updated_at = NOW() - WHERE id = NEW.medicamento_id; - END IF; - RETURN NEW; -END; -$$ LANGUAGE plpgsql; - -DROP TRIGGER IF EXISTS trg_actualizar_stock ON veterinaria.medicamentos_lotes; -CREATE TRIGGER trg_actualizar_stock - AFTER INSERT OR UPDATE OF cantidad_actual ON veterinaria.medicamentos_lotes - FOR EACH ROW - EXECUTE FUNCTION veterinaria.actualizar_stock_medicamento(); - -COMMENT ON FUNCTION veterinaria.actualizar_stock_medicamento() IS 'Actualiza stock_actual en medicamentos cuando cambian los lotes'; - --- Trigger para registrar movimiento en dispensacion -CREATE OR REPLACE FUNCTION veterinaria.registrar_movimiento_dispensacion() -RETURNS TRIGGER AS $$ -DECLARE - v_stock_anterior INTEGER; -BEGIN - -- Obtener stock anterior - SELECT stock_actual INTO v_stock_anterior - FROM veterinaria.medicamentos - WHERE id = NEW.medicamento_id; - - -- Descontar del lote - UPDATE veterinaria.medicamentos_lotes - SET cantidad_actual = cantidad_actual - NEW.cantidad, - updated_at = NOW() - WHERE id = NEW.lote_id; - - -- Registrar movimiento - INSERT INTO veterinaria.movimientos_farmacia ( - tenant_id, medicamento_id, lote_id, - tipo, cantidad, stock_anterior, stock_posterior, - referencia_tipo, referencia_id, usuario_id - ) VALUES ( - NEW.tenant_id, NEW.medicamento_id, NEW.lote_id, - 'salida', NEW.cantidad, v_stock_anterior, v_stock_anterior - NEW.cantidad, - 'dispensacion', NEW.id, NEW.dispensado_por - ); - - -- Si es controlado, registrar en bitacora - IF EXISTS ( - SELECT 1 FROM veterinaria.medicamentos - WHERE id = NEW.medicamento_id AND controlado = true - ) THEN - INSERT INTO veterinaria.bitacora_controlados ( - tenant_id, medicamento_id, lote_id, dispensacion_id, - tipo_movimiento, cantidad, mascota_id, - propietario_nombre, receta_id, veterinario_id, - justificacion, registrado_por - ) - SELECT - NEW.tenant_id, NEW.medicamento_id, NEW.lote_id, NEW.id, - 'salida', NEW.cantidad, NEW.mascota_id, - CONCAT(p.nombre, ' ', COALESCE(p.apellidos, '')), - NEW.receta_id, NEW.veterinario_id, - COALESCE(NEW.notas, 'Dispensacion de medicamento'), - NEW.dispensado_por - FROM veterinaria.mascotas m - JOIN veterinaria.propietarios p ON m.propietario_id = p.id - WHERE m.id = NEW.mascota_id; - END IF; - - RETURN NEW; -END; -$$ LANGUAGE plpgsql; - -DROP TRIGGER IF EXISTS trg_registrar_dispensacion ON veterinaria.dispensaciones; -CREATE TRIGGER trg_registrar_dispensacion - AFTER INSERT ON veterinaria.dispensaciones - FOR EACH ROW - EXECUTE FUNCTION veterinaria.registrar_movimiento_dispensacion(); - -COMMENT ON FUNCTION veterinaria.registrar_movimiento_dispensacion() IS 'Registra movimiento y bitacora al dispensar medicamentos'; - --- ============================================================================ --- FUNCIONES DE CONSULTA --- ============================================================================ - --- Funcion para obtener lotes proximos a caducar -CREATE OR REPLACE FUNCTION veterinaria.get_lotes_proximos_caducar( - p_tenant_id UUID, - p_dias INTEGER DEFAULT 30 -) -RETURNS TABLE ( - lote_id UUID, - medicamento_id UUID, - medicamento_nombre VARCHAR, - numero_lote VARCHAR, - fecha_caducidad DATE, - dias_restantes INTEGER, - cantidad_actual INTEGER -) AS $$ -BEGIN - RETURN QUERY - SELECT - l.id AS lote_id, - m.id AS medicamento_id, - m.nombre AS medicamento_nombre, - l.numero_lote, - l.fecha_caducidad, - (l.fecha_caducidad - CURRENT_DATE)::INTEGER AS dias_restantes, - l.cantidad_actual - FROM veterinaria.medicamentos_lotes l - JOIN veterinaria.medicamentos m ON l.medicamento_id = m.id - WHERE l.tenant_id = p_tenant_id - AND l.cantidad_actual > 0 - AND l.bloqueado = false - AND l.fecha_caducidad <= CURRENT_DATE + p_dias - ORDER BY l.fecha_caducidad ASC; -END; -$$ LANGUAGE plpgsql; - -COMMENT ON FUNCTION veterinaria.get_lotes_proximos_caducar(UUID, INTEGER) IS 'Obtiene lotes que caducaran en los proximos N dias'; - --- Funcion para obtener medicamentos con stock bajo -CREATE OR REPLACE FUNCTION veterinaria.get_medicamentos_stock_bajo(p_tenant_id UUID) -RETURNS TABLE ( - medicamento_id UUID, - codigo VARCHAR, - nombre VARCHAR, - stock_actual INTEGER, - stock_minimo INTEGER, - diferencia INTEGER -) AS $$ -BEGIN - RETURN QUERY - SELECT - m.id AS medicamento_id, - m.codigo, - m.nombre, - m.stock_actual, - m.stock_minimo, - (m.stock_minimo - m.stock_actual) AS diferencia - FROM veterinaria.medicamentos m - WHERE m.tenant_id = p_tenant_id - AND m.active = true - AND m.stock_actual <= m.stock_minimo - ORDER BY diferencia DESC; -END; -$$ LANGUAGE plpgsql; - -COMMENT ON FUNCTION veterinaria.get_medicamentos_stock_bajo(UUID) IS 'Obtiene medicamentos con stock igual o menor al minimo'; - --- Funcion para seleccionar lote FEFO (First Expired, First Out) -CREATE OR REPLACE FUNCTION veterinaria.seleccionar_lote_fefo( - p_medicamento_id UUID, - p_cantidad INTEGER -) -RETURNS UUID AS $$ -DECLARE - v_lote_id UUID; -BEGIN - SELECT id INTO v_lote_id - FROM veterinaria.medicamentos_lotes - WHERE medicamento_id = p_medicamento_id - AND cantidad_actual >= p_cantidad - AND bloqueado = false - AND fecha_caducidad > CURRENT_DATE - ORDER BY fecha_caducidad ASC - LIMIT 1; - - IF v_lote_id IS NULL THEN - RAISE EXCEPTION 'No hay lotes disponibles con stock suficiente para el medicamento %', p_medicamento_id; - END IF; - - RETURN v_lote_id; -END; -$$ LANGUAGE plpgsql; - -COMMENT ON FUNCTION veterinaria.seleccionar_lote_fefo(UUID, INTEGER) IS 'Selecciona el lote con fecha de caducidad mas proxima (FEFO)'; - --- ============================================================================ --- FIN VETERINARIA FARMACIA SCHEMA --- ============================================================================ diff --git a/database/seeds/fase8/01-veterinaria-catalogos.sql b/database/seeds/fase8/01-veterinaria-catalogos.sql deleted file mode 100644 index d0fff84..0000000 --- a/database/seeds/fase8/01-veterinaria-catalogos.sql +++ /dev/null @@ -1,146 +0,0 @@ --- ============================================================================ --- SEED DATA: Catálogos de Veterinaria --- Especialización de ERP-Clínicas --- ============================================================================ --- NOTA: Ejecutar después de SET app.current_tenant_id = 'UUID-DEL-TENANT'; --- ============================================================================ - --- Especies -INSERT INTO veterinaria.especies (tenant_id, nombre, nombre_cientifico) -SELECT current_setting('app.current_tenant_id', true)::UUID, nombre, nombre_cientifico -FROM (VALUES - ('Perro', 'Canis lupus familiaris'), - ('Gato', 'Felis silvestris catus'), - ('Ave', NULL), - ('Reptil', NULL), - ('Roedor', NULL), - ('Conejo', 'Oryctolagus cuniculus'), - ('Pez', NULL), - ('Hurón', 'Mustela putorius furo'), - ('Otro', NULL) -) AS t(nombre, nombre_cientifico) -WHERE current_setting('app.current_tenant_id', true) IS NOT NULL - AND current_setting('app.current_tenant_id', true) != '' -ON CONFLICT DO NOTHING; - --- Razas de perro -INSERT INTO veterinaria.razas (tenant_id, especie_id, nombre, tamanio_promedio, peso_promedio_kg) -SELECT - current_setting('app.current_tenant_id', true)::UUID, - e.id, - r.nombre, - r.tamanio, - r.peso -FROM veterinaria.especies e -CROSS JOIN (VALUES - ('Mestizo', 'mediano', 15.0), - ('Chihuahua', 'pequeño', 2.5), - ('Poodle', 'pequeño', 5.0), - ('Bulldog Francés', 'pequeño', 12.0), - ('Beagle', 'mediano', 12.0), - ('Labrador Retriever', 'grande', 30.0), - ('Golden Retriever', 'grande', 32.0), - ('Pastor Alemán', 'grande', 35.0), - ('Rottweiler', 'grande', 45.0), - ('Husky Siberiano', 'grande', 25.0), - ('Pug', 'pequeño', 8.0), - ('Yorkshire Terrier', 'pequeño', 3.0), - ('Schnauzer', 'mediano', 7.0), - ('Boxer', 'grande', 30.0), - ('Pitbull', 'mediano', 25.0) -) AS r(nombre, tamanio, peso) -WHERE e.nombre = 'Perro' - AND e.tenant_id = current_setting('app.current_tenant_id', true)::UUID -ON CONFLICT DO NOTHING; - --- Razas de gato -INSERT INTO veterinaria.razas (tenant_id, especie_id, nombre, tamanio_promedio, peso_promedio_kg) -SELECT - current_setting('app.current_tenant_id', true)::UUID, - e.id, - r.nombre, - r.tamanio, - r.peso -FROM veterinaria.especies e -CROSS JOIN (VALUES - ('Mestizo', 'mediano', 4.0), - ('Siamés', 'mediano', 4.5), - ('Persa', 'mediano', 5.0), - ('Maine Coon', 'grande', 8.0), - ('Bengalí', 'mediano', 5.5), - ('Ragdoll', 'grande', 7.0), - ('British Shorthair', 'mediano', 6.0), - ('Angora', 'mediano', 4.5), - ('Sphynx', 'mediano', 4.0), - ('Abisinio', 'mediano', 4.0) -) AS r(nombre, tamanio, peso) -WHERE e.nombre = 'Gato' - AND e.tenant_id = current_setting('app.current_tenant_id', true)::UUID -ON CONFLICT DO NOTHING; - --- Vacunas para perros -INSERT INTO veterinaria.vacunas (tenant_id, especie_id, nombre, descripcion, intervalo_refuerzo_dias, es_obligatoria) -SELECT - current_setting('app.current_tenant_id', true)::UUID, - e.id, - v.nombre, - v.descripcion, - v.intervalo, - v.obligatoria -FROM veterinaria.especies e -CROSS JOIN (VALUES - ('Parvovirus', 'Protege contra parvovirus canino', 365, false), - ('Moquillo', 'Protege contra distemper canino', 365, false), - ('Hepatitis', 'Protege contra hepatitis infecciosa canina', 365, false), - ('Rabia', 'Vacuna antirrábica - OBLIGATORIA', 365, true), - ('Leptospirosis', 'Protege contra leptospirosis', 365, false), - ('Bordetella', 'Protege contra tos de las perreras', 180, false), - ('Cuádruple', 'Moquillo, Hepatitis, Parvo, Parainfluenza', 365, false), - ('Séxtuple', 'Cuádruple + Coronavirus + Leptospira', 365, false) -) AS v(nombre, descripcion, intervalo, obligatoria) -WHERE e.nombre = 'Perro' - AND e.tenant_id = current_setting('app.current_tenant_id', true)::UUID -ON CONFLICT DO NOTHING; - --- Vacunas para gatos -INSERT INTO veterinaria.vacunas (tenant_id, especie_id, nombre, descripcion, intervalo_refuerzo_dias, es_obligatoria) -SELECT - current_setting('app.current_tenant_id', true)::UUID, - e.id, - v.nombre, - v.descripcion, - v.intervalo, - v.obligatoria -FROM veterinaria.especies e -CROSS JOIN (VALUES - ('Triple Felina', 'Rinotraqueitis, Calicivirus, Panleucopenia', 365, false), - ('Leucemia Felina', 'Protege contra FeLV', 365, false), - ('Rabia', 'Vacuna antirrábica', 365, true), - ('PIF', 'Peritonitis Infecciosa Felina', 365, false) -) AS v(nombre, descripcion, intervalo, obligatoria) -WHERE e.nombre = 'Gato' - AND e.tenant_id = current_setting('app.current_tenant_id', true)::UUID -ON CONFLICT DO NOTHING; - --- Skills específicos veterinarios -INSERT INTO hr.skills (tenant_id, skill_type_id, name, requiere_cedula) -SELECT - current_setting('app.current_tenant_id', true)::UUID, - st.id, - unnest(ARRAY[ - 'Medicina Veterinaria General', - 'Cirugía Veterinaria', - 'Dermatología Veterinaria', - 'Cardiología Veterinaria', - 'Oftalmología Veterinaria', - 'Ortopedia Veterinaria', - 'Oncología Veterinaria', - 'Medicina de Exóticos', - 'Anestesiología Veterinaria', - 'Imagenología Veterinaria' - ]), - true -FROM hr.skill_types st -WHERE st.name = 'Especialidad Médica' - AND st.tenant_id = current_setting('app.current_tenant_id', true)::UUID -ON CONFLICT DO NOTHING; diff --git a/frontend b/frontend new file mode 160000 index 0000000..35f98c8 --- /dev/null +++ b/frontend @@ -0,0 +1 @@ +Subproject commit 35f98c825860ee86b4de996385f7f3d22e50e7cc