Migración desde erp-clinicas/database - Estándar multi-repo v2
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
241058d159
commit
cf07a84e26
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
node_modules/
|
||||
dist/
|
||||
coverage/
|
||||
.env
|
||||
222
HERENCIA-ERP-CORE.md
Normal file
222
HERENCIA-ERP-CORE.md
Normal file
@ -0,0 +1,222 @@
|
||||
# Herencia de Base de Datos - ERP Core -> Clínicas
|
||||
|
||||
**Fecha:** 2025-12-08
|
||||
**Versión:** 1.0
|
||||
**Vertical:** Clínicas
|
||||
**Nivel:** 2B.2
|
||||
|
||||
---
|
||||
|
||||
## RESUMEN
|
||||
|
||||
La vertical de Clínicas hereda los schemas base del ERP Core y extiende con schemas específicos del dominio de gestión médica y expediente clínico.
|
||||
|
||||
**Ubicación DDL Core:** `apps/erp-core/database/ddl/`
|
||||
|
||||
---
|
||||
|
||||
## ARQUITECTURA DE HERENCIA
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ ERP CORE (Base) │
|
||||
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
|
||||
│ │ auth │ │ core │ │financial│ │inventory│ │ hr │ │
|
||||
│ │ 26 tbl │ │ 12 tbl │ │ 15 tbl │ │ 15 tbl │ │ 6 tbl │ │
|
||||
│ └─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │
|
||||
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
|
||||
│ │ sales │ │analytics│ │ system │ │ crm │ │
|
||||
│ │ 6 tbl │ │ 5 tbl │ │ 10 tbl │ │ 5 tbl │ │
|
||||
│ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │
|
||||
│ TOTAL: ~100 tablas heredadas │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
│ HEREDA
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ CLÍNICAS (Extensiones) │
|
||||
│ ┌───────────────┐ ┌───────────────┐ ┌───────────────┐ │
|
||||
│ │ medical │ │ appointments │ │ patients │ │
|
||||
│ │ (expediente) │ │ (citas) │ │ (pacientes) │ │
|
||||
│ └───────────────┘ └───────────────┘ └───────────────┘ │
|
||||
│ EXTENSIONES: ~35 tablas (planificadas) │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## SCHEMAS HEREDADOS DEL CORE
|
||||
|
||||
| Schema | Tablas | Uso en Clínicas |
|
||||
|--------|--------|-----------------|
|
||||
| `auth` | 26 | Autenticación, usuarios médicos |
|
||||
| `core` | 12 | Partners (pacientes), catálogos |
|
||||
| `financial` | 15 | Facturas de servicios médicos |
|
||||
| `inventory` | 15 | Medicamentos, insumos |
|
||||
| `hr` | 6 | Personal médico |
|
||||
| `sales` | 6 | Servicios médicos |
|
||||
| `crm` | 5 | Seguimiento de pacientes |
|
||||
| `analytics` | 5 | Estadísticas médicas |
|
||||
| `system` | 10 | Recordatorios, notificaciones |
|
||||
|
||||
**Total heredado:** ~100 tablas
|
||||
|
||||
---
|
||||
|
||||
## SCHEMAS ESPECÍFICOS DE CLÍNICAS (Planificados)
|
||||
|
||||
### 1. Schema `patients` (estimado 10+ tablas)
|
||||
|
||||
**Propósito:** Gestión de pacientes
|
||||
|
||||
```sql
|
||||
-- Tablas principales planificadas:
|
||||
patients.patients -- Pacientes (extiende core.partners)
|
||||
patients.patient_contacts -- Contactos de emergencia
|
||||
patients.insurance_policies -- Pólizas de seguro
|
||||
patients.medical_history -- Antecedentes médicos
|
||||
patients.allergies -- Alergias
|
||||
patients.family_history -- Antecedentes familiares
|
||||
```
|
||||
|
||||
### 2. Schema `medical` (estimado 15+ tablas)
|
||||
|
||||
**Propósito:** Expediente clínico electrónico
|
||||
|
||||
```sql
|
||||
-- Tablas principales planificadas:
|
||||
medical.consultations -- Consultas médicas
|
||||
medical.diagnoses -- Diagnósticos (CIE-10)
|
||||
medical.prescriptions -- Recetas médicas
|
||||
medical.prescription_lines -- Medicamentos recetados
|
||||
medical.vital_signs -- Signos vitales
|
||||
medical.lab_results -- Resultados de laboratorio
|
||||
medical.imaging_studies -- Estudios de imagen
|
||||
medical.clinical_notes -- Notas clínicas
|
||||
medical.treatments -- Tratamientos
|
||||
```
|
||||
|
||||
### 3. Schema `appointments` (estimado 10+ tablas)
|
||||
|
||||
**Propósito:** Gestión de citas
|
||||
|
||||
```sql
|
||||
-- Tablas principales planificadas:
|
||||
appointments.doctors -- Médicos
|
||||
appointments.specialties -- Especialidades
|
||||
appointments.doctor_schedules -- Horarios de médicos
|
||||
appointments.consulting_rooms -- Consultorios
|
||||
appointments.appointments -- Citas
|
||||
appointments.appointment_types -- Tipos de cita
|
||||
appointments.reminders -- Recordatorios
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## SPECS DEL CORE APLICABLES
|
||||
|
||||
**Documento detallado:** `orchestration/00-guidelines/HERENCIA-SPECS-CORE.md`
|
||||
|
||||
### Correcciones de DDL Core (2025-12-08)
|
||||
|
||||
El DDL del ERP-Core fue corregido para resolver FK inválidas:
|
||||
|
||||
1. **stock_valuation_layers**: Campos `journal_entry_id` y `journal_entry_line_id` (antes `account_move_*`)
|
||||
2. **stock_move_consume_rel**: Nueva tabla de trazabilidad (antes `move_line_consume_rel`)
|
||||
3. **category_stock_accounts**: FK corregida a `core.product_categories`
|
||||
4. **product_categories**: ALTERs ahora apuntan a schema `core`
|
||||
|
||||
### SPECS Obligatorias
|
||||
|
||||
| Spec Core | Aplicación en Clínicas | SP | Estado |
|
||||
|-----------|----------------------|----:|--------|
|
||||
| SPEC-SISTEMA-SECUENCIAS | Foliado de expedientes y citas | 8 | ✅ DDL LISTO |
|
||||
| SPEC-SEGURIDAD-API-KEYS-PERMISOS | Control de acceso a expedientes | 31 | ✅ DDL LISTO |
|
||||
| SPEC-INTEGRACION-CALENDAR | Agenda de citas médicas | 8 | PENDIENTE |
|
||||
| SPEC-RRHH-EVALUACIONES-SKILLS | Credenciales médicas | 26 | ✅ DDL LISTO |
|
||||
| SPEC-MAIL-THREAD-TRACKING | Historial de comunicación | 13 | ✅ DDL LISTO |
|
||||
| SPEC-WIZARD-TRANSIENT-MODEL | Wizards de receta y referencia | 8 | PENDIENTE |
|
||||
| SPEC-FIRMA-ELECTRONICA-NOM151 | Firma de expedientes clínicos | 13 | PENDIENTE |
|
||||
| SPEC-TWO-FACTOR-AUTHENTICATION | Seguridad de acceso | 13 | ✅ DDL LISTO |
|
||||
| SPEC-OAUTH2-SOCIAL-LOGIN | Portal de pacientes | 8 | ✅ DDL LISTO |
|
||||
|
||||
### SPECS Opcionales
|
||||
|
||||
| Spec Core | Decisión | Razón |
|
||||
|-----------|----------|-------|
|
||||
| SPEC-VALORACION-INVENTARIO | EVALUAR | Solo si hay farmacia interna |
|
||||
| SPEC-PRICING-RULES | EVALUAR | Para paquetes de servicios |
|
||||
| SPEC-TAREAS-RECURRENTES | EVALUAR | Para citas periódicas |
|
||||
|
||||
### SPECS No Aplican
|
||||
|
||||
| Spec Core | Razón |
|
||||
|-----------|-------|
|
||||
| SPEC-PORTAL-PROVEEDORES | No hay compras complejas |
|
||||
| SPEC-BLANKET-ORDERS | No aplica en servicios médicos |
|
||||
| SPEC-INVENTARIOS-CICLICOS | Solo si hay farmacia grande |
|
||||
| SPEC-PROYECTOS-DEPENDENCIAS-BURNDOWN | No hay proyectos de este tipo |
|
||||
|
||||
### Cumplimiento Normativo
|
||||
|
||||
| Norma | Descripción | SPECS Relacionadas |
|
||||
|-------|-------------|-------------------|
|
||||
| NOM-024-SSA3-2012 | Expediente clínico electrónico | SPEC-SEGURIDAD, SPEC-MAIL-THREAD |
|
||||
| LFPDPPP | Protección de datos personales | SPEC-SEGURIDAD, SPEC-2FA |
|
||||
| NOM-004-SSA3-2012 | Expediente clínico | SPEC-FIRMA-ELECTRONICA |
|
||||
|
||||
---
|
||||
|
||||
## CUMPLIMIENTO NORMATIVO
|
||||
|
||||
Este sistema debe cumplir con:
|
||||
|
||||
| Norma | Descripción | Impacto |
|
||||
|-------|-------------|---------|
|
||||
| NOM-024-SSA3-2012 | Expediente clínico electrónico | Estructura de datos |
|
||||
| LFPDPPP | Protección de datos personales | Seguridad y acceso |
|
||||
| NOM-004-SSA3-2012 | Expediente clínico | Contenido mínimo |
|
||||
|
||||
---
|
||||
|
||||
## ORDEN DE EJECUCIÓN DDL (Futuro)
|
||||
|
||||
```bash
|
||||
# PASO 1: Cargar ERP Core (base)
|
||||
cd apps/erp-core/database
|
||||
./scripts/reset-database.sh --force
|
||||
|
||||
# PASO 2: Cargar extensiones de Clínicas
|
||||
cd apps/verticales/clinicas/database
|
||||
psql $DATABASE_URL -f init/00-extensions.sql
|
||||
psql $DATABASE_URL -f init/01-create-schemas.sql
|
||||
psql $DATABASE_URL -f init/02-patients-tables.sql
|
||||
psql $DATABASE_URL -f init/03-medical-tables.sql
|
||||
psql $DATABASE_URL -f init/04-appointments-tables.sql
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## MAPEO DE NOMENCLATURA
|
||||
|
||||
| Core | Clínicas |
|
||||
|------|----------|
|
||||
| `core.partners` | Pacientes base |
|
||||
| `hr.employees` | Personal médico |
|
||||
| `inventory.products` | Medicamentos, insumos |
|
||||
| `sales.sale_orders` | Servicios médicos |
|
||||
| `financial.invoices` | Facturas de consultas |
|
||||
|
||||
---
|
||||
|
||||
## REFERENCIAS
|
||||
|
||||
- ERP Core DDL: `apps/erp-core/database/ddl/`
|
||||
- ERP Core README: `apps/erp-core/database/README.md`
|
||||
- Directivas: `orchestration/directivas/`
|
||||
- Inventarios: `orchestration/inventarios/`
|
||||
|
||||
---
|
||||
|
||||
**Documento de herencia oficial**
|
||||
**Última actualización:** 2025-12-08
|
||||
84
README.md
84
README.md
@ -1,3 +1,83 @@
|
||||
# erp-clinicas-database-v2
|
||||
# Base de Datos - ERP Clínicas
|
||||
|
||||
Database de erp-clinicas - Workspace V2
|
||||
## Resumen
|
||||
|
||||
| Aspecto | Valor |
|
||||
|---------|-------|
|
||||
| **Schema principal** | `clinica` |
|
||||
| **Tablas específicas** | 13 |
|
||||
| **ENUMs** | 4 |
|
||||
| **Hereda de ERP-Core** | 144 tablas (12 schemas) |
|
||||
|
||||
## Prerequisitos
|
||||
|
||||
1. **ERP-Core instalado** con todos sus schemas:
|
||||
- auth, core, financial, inventory, purchase, sales, projects, analytics, system, billing, crm, hr
|
||||
|
||||
2. **Extensiones PostgreSQL**:
|
||||
- pgcrypto (encriptación)
|
||||
- pg_trgm (búsqueda de texto)
|
||||
|
||||
## Orden de Ejecución DDL
|
||||
|
||||
```bash
|
||||
# 1. Instalar ERP-Core primero
|
||||
cd apps/erp-core/database
|
||||
./scripts/reset-database.sh
|
||||
|
||||
# 2. Instalar extensión Clínicas
|
||||
cd apps/verticales/clinicas/database
|
||||
psql $DATABASE_URL -f init/00-extensions.sql
|
||||
psql $DATABASE_URL -f init/01-create-schemas.sql
|
||||
psql $DATABASE_URL -f init/02-rls-functions.sql
|
||||
psql $DATABASE_URL -f init/03-clinical-tables.sql
|
||||
psql $DATABASE_URL -f init/04-seed-data.sql
|
||||
```
|
||||
|
||||
## Tablas Implementadas
|
||||
|
||||
### Schema: clinica (13 tablas)
|
||||
|
||||
| Tabla | Módulo | Descripción |
|
||||
|-------|--------|-------------|
|
||||
| specialties | CL-002 | Catálogo de especialidades médicas |
|
||||
| doctors | CL-002 | Médicos (extiende hr.employees) |
|
||||
| patients | CL-001 | Pacientes (extiende core.partners) |
|
||||
| patient_contacts | CL-001 | Contactos de emergencia |
|
||||
| patient_insurance | CL-001 | Información de seguros |
|
||||
| appointment_slots | CL-002 | Horarios disponibles |
|
||||
| appointments | CL-002 | Citas médicas |
|
||||
| medical_records | CL-003 | Expediente clínico electrónico |
|
||||
| consultations | CL-003 | Consultas realizadas |
|
||||
| vital_signs | CL-003 | Signos vitales |
|
||||
| diagnoses | CL-003 | Diagnósticos (CIE-10) |
|
||||
| prescriptions | CL-003 | Recetas médicas |
|
||||
| prescription_items | CL-003 | Medicamentos en receta |
|
||||
|
||||
## ENUMs
|
||||
|
||||
| Enum | Valores |
|
||||
|------|---------|
|
||||
| appointment_status | scheduled, confirmed, in_progress, completed, cancelled, no_show |
|
||||
| patient_gender | male, female, other, prefer_not_to_say |
|
||||
| blood_type | A+, A-, B+, B-, AB+, AB-, O+, O-, unknown |
|
||||
| consultation_status | draft, in_progress, completed, cancelled |
|
||||
|
||||
## Row Level Security
|
||||
|
||||
Todas las tablas tienen RLS habilitado con aislamiento por tenant:
|
||||
|
||||
```sql
|
||||
tenant_id = current_setting('app.current_tenant_id', true)::UUID
|
||||
```
|
||||
|
||||
## Consideraciones de Seguridad
|
||||
|
||||
- **NOM-024-SSA3-2012**: Expediente clínico electrónico
|
||||
- **Datos sensibles**: medical_records, consultations requieren encriptación
|
||||
- **Auditoría completa**: Todas las tablas tienen campos de auditoría
|
||||
|
||||
## Referencias
|
||||
|
||||
- [HERENCIA-ERP-CORE.md](./HERENCIA-ERP-CORE.md)
|
||||
- [DATABASE_INVENTORY.yml](../orchestration/inventarios/DATABASE_INVENTORY.yml)
|
||||
|
||||
25
init/00-extensions.sql
Normal file
25
init/00-extensions.sql
Normal file
@ -0,0 +1,25 @@
|
||||
-- ============================================================================
|
||||
-- EXTENSIONES PostgreSQL - ERP Clínicas
|
||||
-- ============================================================================
|
||||
-- Versión: 1.0.0
|
||||
-- Fecha: 2025-12-09
|
||||
-- Prerequisito: ERP-Core debe estar instalado
|
||||
-- ============================================================================
|
||||
|
||||
-- Verificar que ERP-Core esté instalado
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_namespace WHERE nspname = 'auth') THEN
|
||||
RAISE EXCEPTION 'ERP-Core no instalado. Ejecutar primero DDL de erp-core.';
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- Extensión para encriptación de datos sensibles (expedientes médicos)
|
||||
CREATE EXTENSION IF NOT EXISTS pgcrypto;
|
||||
|
||||
-- Extensión para búsqueda de texto (diagnósticos CIE-10)
|
||||
CREATE EXTENSION IF NOT EXISTS pg_trgm;
|
||||
|
||||
-- ============================================================================
|
||||
-- FIN EXTENSIONES
|
||||
-- ============================================================================
|
||||
15
init/01-create-schemas.sql
Normal file
15
init/01-create-schemas.sql
Normal file
@ -0,0 +1,15 @@
|
||||
-- ============================================================================
|
||||
-- SCHEMAS - ERP Clínicas
|
||||
-- ============================================================================
|
||||
-- Versión: 1.0.0
|
||||
-- Fecha: 2025-12-09
|
||||
-- ============================================================================
|
||||
|
||||
-- Schema principal para operaciones clínicas
|
||||
CREATE SCHEMA IF NOT EXISTS clinica;
|
||||
|
||||
COMMENT ON SCHEMA clinica IS 'Schema para operaciones de clínica/consultorio médico';
|
||||
|
||||
-- ============================================================================
|
||||
-- FIN SCHEMAS
|
||||
-- ============================================================================
|
||||
37
init/02-rls-functions.sql
Normal file
37
init/02-rls-functions.sql
Normal file
@ -0,0 +1,37 @@
|
||||
-- ============================================================================
|
||||
-- FUNCIONES RLS - ERP Clínicas
|
||||
-- ============================================================================
|
||||
-- Versión: 1.0.0
|
||||
-- Fecha: 2025-12-09
|
||||
-- Nota: Usa las funciones de contexto de ERP-Core (auth schema)
|
||||
-- ============================================================================
|
||||
|
||||
-- Las funciones principales están en ERP-Core:
|
||||
-- auth.get_current_tenant_id()
|
||||
-- auth.get_current_user_id()
|
||||
-- auth.get_current_company_id()
|
||||
|
||||
-- Función auxiliar para verificar acceso a expediente médico
|
||||
CREATE OR REPLACE FUNCTION clinica.can_access_medical_record(
|
||||
p_patient_id UUID,
|
||||
p_user_id UUID DEFAULT NULL
|
||||
)
|
||||
RETURNS BOOLEAN AS $$
|
||||
DECLARE
|
||||
v_user_id UUID;
|
||||
v_has_access BOOLEAN := FALSE;
|
||||
BEGIN
|
||||
v_user_id := COALESCE(p_user_id, current_setting('app.current_user_id', true)::UUID);
|
||||
|
||||
-- TODO: Implementar lógica de permisos específicos
|
||||
-- Por ahora, cualquier usuario del tenant puede acceder
|
||||
RETURN TRUE;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql SECURITY DEFINER;
|
||||
|
||||
COMMENT ON FUNCTION clinica.can_access_medical_record IS
|
||||
'Verifica si el usuario tiene permiso para acceder al expediente médico del paciente';
|
||||
|
||||
-- ============================================================================
|
||||
-- FIN FUNCIONES RLS
|
||||
-- ============================================================================
|
||||
628
init/03-clinical-tables.sql
Normal file
628
init/03-clinical-tables.sql
Normal file
@ -0,0 +1,628 @@
|
||||
-- ============================================================================
|
||||
-- TABLAS CLÍNICAS - ERP Clínicas
|
||||
-- ============================================================================
|
||||
-- Módulos: CL-001 (Pacientes), CL-002 (Citas), CL-003 (Expediente)
|
||||
-- Versión: 1.0.0
|
||||
-- Fecha: 2025-12-09
|
||||
-- ============================================================================
|
||||
-- PREREQUISITOS:
|
||||
-- 1. ERP-Core instalado (auth.tenants, auth.users, core.partners)
|
||||
-- 2. Schema clinica creado
|
||||
-- ============================================================================
|
||||
|
||||
-- ============================================================================
|
||||
-- TYPES (ENUMs)
|
||||
-- ============================================================================
|
||||
|
||||
DO $$ BEGIN
|
||||
CREATE TYPE clinica.appointment_status AS ENUM (
|
||||
'scheduled', 'confirmed', 'in_progress', 'completed', 'cancelled', 'no_show'
|
||||
);
|
||||
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||
|
||||
DO $$ BEGIN
|
||||
CREATE TYPE clinica.patient_gender AS ENUM (
|
||||
'male', 'female', 'other', 'prefer_not_to_say'
|
||||
);
|
||||
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||
|
||||
DO $$ BEGIN
|
||||
CREATE TYPE clinica.blood_type AS ENUM (
|
||||
'A+', 'A-', 'B+', 'B-', 'AB+', 'AB-', 'O+', 'O-', 'unknown'
|
||||
);
|
||||
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||
|
||||
DO $$ BEGIN
|
||||
CREATE TYPE clinica.consultation_status AS ENUM (
|
||||
'draft', 'in_progress', 'completed', 'cancelled'
|
||||
);
|
||||
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||
|
||||
-- ============================================================================
|
||||
-- CATÁLOGOS BASE
|
||||
-- ============================================================================
|
||||
|
||||
-- Tabla: specialties (Especialidades médicas)
|
||||
CREATE TABLE IF NOT EXISTS clinica.specialties (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
|
||||
code VARCHAR(20) NOT NULL,
|
||||
name VARCHAR(100) NOT NULL,
|
||||
description TEXT,
|
||||
consultation_duration INTEGER DEFAULT 30, -- minutos
|
||||
is_active BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
created_by UUID REFERENCES auth.users(id),
|
||||
updated_at TIMESTAMPTZ,
|
||||
updated_by UUID REFERENCES auth.users(id),
|
||||
CONSTRAINT uq_specialties_code UNIQUE (tenant_id, code)
|
||||
);
|
||||
|
||||
-- Tabla: doctors (Médicos - extiende hr.employees)
|
||||
CREATE TABLE IF NOT EXISTS clinica.doctors (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
|
||||
employee_id UUID, -- FK a hr.employees (ERP Core)
|
||||
user_id UUID REFERENCES auth.users(id),
|
||||
specialty_id UUID NOT NULL REFERENCES clinica.specialties(id),
|
||||
license_number VARCHAR(50) NOT NULL, -- Cédula profesional
|
||||
license_expiry DATE,
|
||||
secondary_specialties UUID[], -- Array de specialty_ids
|
||||
consultation_fee DECIMAL(12,2),
|
||||
is_active BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
created_by UUID REFERENCES auth.users(id),
|
||||
updated_at TIMESTAMPTZ,
|
||||
updated_by UUID REFERENCES auth.users(id),
|
||||
deleted_at TIMESTAMPTZ,
|
||||
deleted_by UUID REFERENCES auth.users(id),
|
||||
CONSTRAINT uq_doctors_license UNIQUE (tenant_id, license_number)
|
||||
);
|
||||
|
||||
-- ============================================================================
|
||||
-- PACIENTES (CL-001)
|
||||
-- ============================================================================
|
||||
|
||||
-- Tabla: patients (Pacientes - extiende core.partners)
|
||||
CREATE TABLE IF NOT EXISTS clinica.patients (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
|
||||
partner_id UUID REFERENCES core.partners(id), -- Vinculo a partner
|
||||
|
||||
-- Identificación
|
||||
patient_number VARCHAR(30) NOT NULL,
|
||||
first_name VARCHAR(100) NOT NULL,
|
||||
last_name VARCHAR(100) NOT NULL,
|
||||
middle_name VARCHAR(100),
|
||||
|
||||
-- Datos personales
|
||||
birth_date DATE,
|
||||
gender clinica.patient_gender,
|
||||
curp VARCHAR(18),
|
||||
|
||||
-- Contacto
|
||||
email VARCHAR(255),
|
||||
phone VARCHAR(20),
|
||||
mobile VARCHAR(20),
|
||||
|
||||
-- Dirección
|
||||
street VARCHAR(255),
|
||||
city VARCHAR(100),
|
||||
state VARCHAR(100),
|
||||
zip_code VARCHAR(10),
|
||||
country VARCHAR(100) DEFAULT 'México',
|
||||
|
||||
-- Datos médicos básicos
|
||||
blood_type clinica.blood_type DEFAULT 'unknown',
|
||||
allergies TEXT[],
|
||||
chronic_conditions TEXT[],
|
||||
|
||||
-- Seguro médico
|
||||
has_insurance BOOLEAN DEFAULT FALSE,
|
||||
insurance_provider VARCHAR(100),
|
||||
insurance_policy VARCHAR(50),
|
||||
|
||||
-- Control
|
||||
is_active BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
last_visit_date DATE,
|
||||
|
||||
-- Auditoría
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
created_by UUID REFERENCES auth.users(id),
|
||||
updated_at TIMESTAMPTZ,
|
||||
updated_by UUID REFERENCES auth.users(id),
|
||||
deleted_at TIMESTAMPTZ,
|
||||
deleted_by UUID REFERENCES auth.users(id),
|
||||
|
||||
CONSTRAINT uq_patients_number UNIQUE (tenant_id, patient_number)
|
||||
);
|
||||
|
||||
-- Tabla: patient_contacts (Contactos de emergencia)
|
||||
CREATE TABLE IF NOT EXISTS clinica.patient_contacts (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
|
||||
patient_id UUID NOT NULL REFERENCES clinica.patients(id) ON DELETE CASCADE,
|
||||
contact_name VARCHAR(200) NOT NULL,
|
||||
relationship VARCHAR(50), -- Parentesco
|
||||
phone VARCHAR(20),
|
||||
mobile VARCHAR(20),
|
||||
email VARCHAR(255),
|
||||
is_primary BOOLEAN DEFAULT FALSE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
created_by UUID REFERENCES auth.users(id),
|
||||
updated_at TIMESTAMPTZ,
|
||||
updated_by UUID REFERENCES auth.users(id)
|
||||
);
|
||||
|
||||
-- Tabla: patient_insurance (Información de seguros)
|
||||
CREATE TABLE IF NOT EXISTS clinica.patient_insurance (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
|
||||
patient_id UUID NOT NULL REFERENCES clinica.patients(id) ON DELETE CASCADE,
|
||||
insurance_provider VARCHAR(100) NOT NULL,
|
||||
policy_number VARCHAR(50) NOT NULL,
|
||||
group_number VARCHAR(50),
|
||||
holder_name VARCHAR(200),
|
||||
holder_relationship VARCHAR(50),
|
||||
coverage_type VARCHAR(50),
|
||||
valid_from DATE,
|
||||
valid_until DATE,
|
||||
is_primary BOOLEAN DEFAULT TRUE,
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
created_by UUID REFERENCES auth.users(id),
|
||||
updated_at TIMESTAMPTZ,
|
||||
updated_by UUID REFERENCES auth.users(id)
|
||||
);
|
||||
|
||||
-- ============================================================================
|
||||
-- CITAS (CL-002)
|
||||
-- ============================================================================
|
||||
|
||||
-- Tabla: appointment_slots (Horarios disponibles)
|
||||
CREATE TABLE IF NOT EXISTS clinica.appointment_slots (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
|
||||
doctor_id UUID NOT NULL REFERENCES clinica.doctors(id),
|
||||
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 INTEGER DEFAULT 30, -- minutos
|
||||
max_appointments INTEGER DEFAULT 1,
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
created_by UUID REFERENCES auth.users(id),
|
||||
updated_at TIMESTAMPTZ,
|
||||
updated_by UUID REFERENCES auth.users(id),
|
||||
CONSTRAINT chk_slot_times CHECK (end_time > start_time)
|
||||
);
|
||||
|
||||
-- Tabla: appointments (Citas médicas)
|
||||
CREATE TABLE IF NOT EXISTS clinica.appointments (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
|
||||
|
||||
-- Referencias
|
||||
patient_id UUID NOT NULL REFERENCES clinica.patients(id),
|
||||
doctor_id UUID NOT NULL REFERENCES clinica.doctors(id),
|
||||
specialty_id UUID REFERENCES clinica.specialties(id),
|
||||
|
||||
-- Programación
|
||||
appointment_date DATE NOT NULL,
|
||||
start_time TIME NOT NULL,
|
||||
end_time TIME NOT NULL,
|
||||
duration INTEGER DEFAULT 30, -- minutos
|
||||
|
||||
-- Estado
|
||||
status clinica.appointment_status NOT NULL DEFAULT 'scheduled',
|
||||
|
||||
-- Detalles
|
||||
reason TEXT, -- Motivo de consulta
|
||||
notes TEXT,
|
||||
is_first_visit BOOLEAN DEFAULT FALSE,
|
||||
is_follow_up BOOLEAN DEFAULT FALSE,
|
||||
follow_up_to UUID REFERENCES clinica.appointments(id),
|
||||
|
||||
-- Recordatorios
|
||||
reminder_sent BOOLEAN DEFAULT FALSE,
|
||||
reminder_sent_at TIMESTAMPTZ,
|
||||
|
||||
-- Confirmación
|
||||
confirmed_at TIMESTAMPTZ,
|
||||
confirmed_by UUID REFERENCES auth.users(id),
|
||||
|
||||
-- Cancelación
|
||||
cancelled_at TIMESTAMPTZ,
|
||||
cancelled_by UUID REFERENCES auth.users(id),
|
||||
cancellation_reason TEXT,
|
||||
|
||||
-- Auditoría
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
created_by UUID REFERENCES auth.users(id),
|
||||
updated_at TIMESTAMPTZ,
|
||||
updated_by UUID REFERENCES auth.users(id),
|
||||
|
||||
CONSTRAINT chk_appointment_times CHECK (end_time > start_time)
|
||||
);
|
||||
|
||||
-- ============================================================================
|
||||
-- EXPEDIENTE CLÍNICO (CL-003)
|
||||
-- ============================================================================
|
||||
|
||||
-- Tabla: medical_records (Expediente clínico electrónico)
|
||||
-- NOTA: Datos sensibles según NOM-024-SSA3-2012
|
||||
CREATE TABLE IF NOT EXISTS clinica.medical_records (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
|
||||
patient_id UUID NOT NULL REFERENCES clinica.patients(id),
|
||||
|
||||
-- Número de expediente
|
||||
record_number VARCHAR(30) NOT NULL,
|
||||
|
||||
-- Antecedentes
|
||||
family_history TEXT,
|
||||
personal_history TEXT,
|
||||
surgical_history TEXT,
|
||||
|
||||
-- Hábitos
|
||||
smoking_status VARCHAR(50),
|
||||
alcohol_status VARCHAR(50),
|
||||
exercise_status VARCHAR(50),
|
||||
diet_notes TEXT,
|
||||
|
||||
-- Gineco-obstétricos (si aplica)
|
||||
obstetric_history JSONB,
|
||||
|
||||
-- Notas generales
|
||||
notes TEXT,
|
||||
|
||||
-- Control de acceso
|
||||
is_confidential BOOLEAN DEFAULT TRUE,
|
||||
access_restricted BOOLEAN DEFAULT FALSE,
|
||||
|
||||
-- Auditoría
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
created_by UUID REFERENCES auth.users(id),
|
||||
updated_at TIMESTAMPTZ,
|
||||
updated_by UUID REFERENCES auth.users(id),
|
||||
|
||||
CONSTRAINT uq_medical_records_number UNIQUE (tenant_id, record_number),
|
||||
CONSTRAINT uq_medical_records_patient UNIQUE (patient_id)
|
||||
);
|
||||
|
||||
-- Tabla: consultations (Consultas realizadas)
|
||||
CREATE TABLE IF NOT EXISTS clinica.consultations (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
|
||||
|
||||
-- Referencias
|
||||
medical_record_id UUID NOT NULL REFERENCES clinica.medical_records(id),
|
||||
appointment_id UUID REFERENCES clinica.appointments(id),
|
||||
doctor_id UUID NOT NULL REFERENCES clinica.doctors(id),
|
||||
|
||||
-- Fecha/hora
|
||||
consultation_date DATE NOT NULL,
|
||||
start_time TIMESTAMPTZ,
|
||||
end_time TIMESTAMPTZ,
|
||||
|
||||
-- Estado
|
||||
status clinica.consultation_status DEFAULT 'draft',
|
||||
|
||||
-- Motivo de consulta
|
||||
chief_complaint TEXT NOT NULL, -- Motivo principal
|
||||
present_illness TEXT, -- Padecimiento actual
|
||||
|
||||
-- Exploración física
|
||||
physical_exam JSONB, -- Estructurado por sistemas
|
||||
|
||||
-- Plan
|
||||
treatment_plan TEXT,
|
||||
follow_up_instructions TEXT,
|
||||
next_appointment_days INTEGER,
|
||||
|
||||
-- Notas
|
||||
notes TEXT,
|
||||
private_notes TEXT, -- Solo visible para el médico
|
||||
|
||||
-- Auditoría
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
created_by UUID REFERENCES auth.users(id),
|
||||
updated_at TIMESTAMPTZ,
|
||||
updated_by UUID REFERENCES auth.users(id)
|
||||
);
|
||||
|
||||
-- Tabla: vital_signs (Signos vitales)
|
||||
CREATE TABLE IF NOT EXISTS clinica.vital_signs (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
|
||||
consultation_id UUID NOT NULL REFERENCES clinica.consultations(id) ON DELETE CASCADE,
|
||||
|
||||
-- Signos vitales
|
||||
weight_kg DECIMAL(5,2),
|
||||
height_cm DECIMAL(5,2),
|
||||
bmi DECIMAL(4,2) GENERATED ALWAYS AS (
|
||||
CASE WHEN height_cm > 0 THEN weight_kg / ((height_cm/100) * (height_cm/100)) END
|
||||
) STORED,
|
||||
temperature_c DECIMAL(4,2),
|
||||
blood_pressure_systolic INTEGER,
|
||||
blood_pressure_diastolic INTEGER,
|
||||
heart_rate INTEGER, -- latidos por minuto
|
||||
respiratory_rate INTEGER, -- respiraciones por minuto
|
||||
oxygen_saturation INTEGER, -- porcentaje
|
||||
|
||||
-- Fecha/hora de medición
|
||||
measured_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
measured_by UUID REFERENCES auth.users(id),
|
||||
|
||||
notes TEXT,
|
||||
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
created_by UUID REFERENCES auth.users(id)
|
||||
);
|
||||
|
||||
-- Tabla: diagnoses (Diagnósticos - CIE-10)
|
||||
CREATE TABLE IF NOT EXISTS clinica.diagnoses (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
|
||||
consultation_id UUID NOT NULL REFERENCES clinica.consultations(id) ON DELETE CASCADE,
|
||||
|
||||
-- Código CIE-10
|
||||
icd10_code VARCHAR(10) NOT NULL,
|
||||
icd10_description VARCHAR(255),
|
||||
|
||||
-- Tipo
|
||||
diagnosis_type VARCHAR(20) NOT NULL DEFAULT 'primary', -- primary, secondary, differential
|
||||
|
||||
-- Detalles
|
||||
notes TEXT,
|
||||
is_chronic BOOLEAN DEFAULT FALSE,
|
||||
onset_date DATE,
|
||||
|
||||
-- Orden
|
||||
sequence INTEGER DEFAULT 1,
|
||||
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
created_by UUID REFERENCES auth.users(id)
|
||||
);
|
||||
|
||||
-- Tabla: prescriptions (Recetas médicas)
|
||||
CREATE TABLE IF NOT EXISTS clinica.prescriptions (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
|
||||
consultation_id UUID NOT NULL REFERENCES clinica.consultations(id),
|
||||
|
||||
-- Número de receta
|
||||
prescription_number VARCHAR(30) NOT NULL,
|
||||
prescription_date DATE NOT NULL DEFAULT CURRENT_DATE,
|
||||
|
||||
-- Médico
|
||||
doctor_id UUID NOT NULL REFERENCES clinica.doctors(id),
|
||||
|
||||
-- Instrucciones generales
|
||||
general_instructions TEXT,
|
||||
|
||||
-- Vigencia
|
||||
valid_until DATE,
|
||||
|
||||
-- Estado
|
||||
is_printed BOOLEAN DEFAULT FALSE,
|
||||
printed_at TIMESTAMPTZ,
|
||||
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
created_by UUID REFERENCES auth.users(id),
|
||||
updated_at TIMESTAMPTZ,
|
||||
updated_by UUID REFERENCES auth.users(id),
|
||||
|
||||
CONSTRAINT uq_prescriptions_number UNIQUE (tenant_id, prescription_number)
|
||||
);
|
||||
|
||||
-- Tabla: prescription_items (Líneas de receta)
|
||||
CREATE TABLE IF NOT EXISTS clinica.prescription_items (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
|
||||
prescription_id UUID NOT NULL REFERENCES clinica.prescriptions(id) ON DELETE CASCADE,
|
||||
|
||||
-- Medicamento
|
||||
product_id UUID, -- FK a inventory.products (ERP Core)
|
||||
medication_name VARCHAR(255) NOT NULL,
|
||||
presentation VARCHAR(100), -- Tabletas, jarabe, etc.
|
||||
|
||||
-- Dosificación
|
||||
dosage VARCHAR(100) NOT NULL, -- "1 tableta"
|
||||
frequency VARCHAR(100) NOT NULL, -- "cada 8 horas"
|
||||
duration VARCHAR(100), -- "por 7 días"
|
||||
quantity INTEGER, -- Cantidad a surtir
|
||||
|
||||
-- Instrucciones
|
||||
instructions TEXT,
|
||||
|
||||
-- Orden
|
||||
sequence INTEGER DEFAULT 1,
|
||||
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
created_by UUID REFERENCES auth.users(id)
|
||||
);
|
||||
|
||||
-- ============================================================================
|
||||
-- ÍNDICES
|
||||
-- ============================================================================
|
||||
|
||||
-- Specialties
|
||||
CREATE INDEX IF NOT EXISTS idx_specialties_tenant ON clinica.specialties(tenant_id);
|
||||
|
||||
-- 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_user ON clinica.doctors(user_id);
|
||||
|
||||
-- Patients
|
||||
CREATE INDEX IF NOT EXISTS idx_patients_tenant ON clinica.patients(tenant_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_patients_partner ON clinica.patients(partner_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_patients_name ON clinica.patients(last_name, first_name);
|
||||
CREATE INDEX IF NOT EXISTS idx_patients_curp ON clinica.patients(curp);
|
||||
|
||||
-- Patient contacts
|
||||
CREATE INDEX IF NOT EXISTS idx_patient_contacts_tenant ON clinica.patient_contacts(tenant_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_patient_contacts_patient ON clinica.patient_contacts(patient_id);
|
||||
|
||||
-- Patient insurance
|
||||
CREATE INDEX IF NOT EXISTS idx_patient_insurance_tenant ON clinica.patient_insurance(tenant_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_patient_insurance_patient ON clinica.patient_insurance(patient_id);
|
||||
|
||||
-- Appointment slots
|
||||
CREATE INDEX IF NOT EXISTS idx_appointment_slots_tenant ON clinica.appointment_slots(tenant_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_appointment_slots_doctor ON clinica.appointment_slots(doctor_id);
|
||||
|
||||
-- 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(appointment_date);
|
||||
CREATE INDEX IF NOT EXISTS idx_appointments_status ON clinica.appointments(status);
|
||||
|
||||
-- Medical records
|
||||
CREATE INDEX IF NOT EXISTS idx_medical_records_tenant ON clinica.medical_records(tenant_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_medical_records_patient ON clinica.medical_records(patient_id);
|
||||
|
||||
-- Consultations
|
||||
CREATE INDEX IF NOT EXISTS idx_consultations_tenant ON clinica.consultations(tenant_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_consultations_record ON clinica.consultations(medical_record_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(consultation_date);
|
||||
|
||||
-- Vital signs
|
||||
CREATE INDEX IF NOT EXISTS idx_vital_signs_tenant ON clinica.vital_signs(tenant_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_vital_signs_consultation ON clinica.vital_signs(consultation_id);
|
||||
|
||||
-- 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);
|
||||
|
||||
-- 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);
|
||||
|
||||
-- Prescription items
|
||||
CREATE INDEX IF NOT EXISTS idx_prescription_items_tenant ON clinica.prescription_items(tenant_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_prescription_items_prescription ON clinica.prescription_items(prescription_id);
|
||||
|
||||
-- ============================================================================
|
||||
-- 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.patient_contacts ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE clinica.patient_insurance ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE clinica.appointment_slots ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE clinica.appointments ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE clinica.medical_records ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE clinica.consultations ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE clinica.vital_signs 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;
|
||||
|
||||
-- Políticas de aislamiento por tenant
|
||||
DO $$ BEGIN
|
||||
DROP POLICY IF EXISTS tenant_isolation_specialties ON clinica.specialties;
|
||||
CREATE POLICY tenant_isolation_specialties ON clinica.specialties
|
||||
FOR ALL USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
|
||||
EXCEPTION WHEN undefined_object THEN NULL; END $$;
|
||||
|
||||
DO $$ BEGIN
|
||||
DROP POLICY IF EXISTS tenant_isolation_doctors ON clinica.doctors;
|
||||
CREATE POLICY tenant_isolation_doctors ON clinica.doctors
|
||||
FOR ALL USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
|
||||
EXCEPTION WHEN undefined_object THEN NULL; END $$;
|
||||
|
||||
DO $$ BEGIN
|
||||
DROP POLICY IF EXISTS tenant_isolation_patients ON clinica.patients;
|
||||
CREATE POLICY tenant_isolation_patients ON clinica.patients
|
||||
FOR ALL USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
|
||||
EXCEPTION WHEN undefined_object THEN NULL; END $$;
|
||||
|
||||
DO $$ BEGIN
|
||||
DROP POLICY IF EXISTS tenant_isolation_patient_contacts ON clinica.patient_contacts;
|
||||
CREATE POLICY tenant_isolation_patient_contacts ON clinica.patient_contacts
|
||||
FOR ALL USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
|
||||
EXCEPTION WHEN undefined_object THEN NULL; END $$;
|
||||
|
||||
DO $$ BEGIN
|
||||
DROP POLICY IF EXISTS tenant_isolation_patient_insurance ON clinica.patient_insurance;
|
||||
CREATE POLICY tenant_isolation_patient_insurance ON clinica.patient_insurance
|
||||
FOR ALL USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
|
||||
EXCEPTION WHEN undefined_object THEN NULL; END $$;
|
||||
|
||||
DO $$ BEGIN
|
||||
DROP POLICY IF EXISTS tenant_isolation_appointment_slots ON clinica.appointment_slots;
|
||||
CREATE POLICY tenant_isolation_appointment_slots ON clinica.appointment_slots
|
||||
FOR ALL USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
|
||||
EXCEPTION WHEN undefined_object THEN NULL; END $$;
|
||||
|
||||
DO $$ BEGIN
|
||||
DROP POLICY IF EXISTS tenant_isolation_appointments ON clinica.appointments;
|
||||
CREATE POLICY tenant_isolation_appointments ON clinica.appointments
|
||||
FOR ALL USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
|
||||
EXCEPTION WHEN undefined_object THEN NULL; END $$;
|
||||
|
||||
DO $$ BEGIN
|
||||
DROP POLICY IF EXISTS tenant_isolation_medical_records ON clinica.medical_records;
|
||||
CREATE POLICY tenant_isolation_medical_records ON clinica.medical_records
|
||||
FOR ALL USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
|
||||
EXCEPTION WHEN undefined_object THEN NULL; END $$;
|
||||
|
||||
DO $$ BEGIN
|
||||
DROP POLICY IF EXISTS tenant_isolation_consultations ON clinica.consultations;
|
||||
CREATE POLICY tenant_isolation_consultations ON clinica.consultations
|
||||
FOR ALL USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
|
||||
EXCEPTION WHEN undefined_object THEN NULL; END $$;
|
||||
|
||||
DO $$ BEGIN
|
||||
DROP POLICY IF EXISTS tenant_isolation_vital_signs ON clinica.vital_signs;
|
||||
CREATE POLICY tenant_isolation_vital_signs ON clinica.vital_signs
|
||||
FOR ALL USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
|
||||
EXCEPTION WHEN undefined_object THEN NULL; END $$;
|
||||
|
||||
DO $$ BEGIN
|
||||
DROP POLICY IF EXISTS tenant_isolation_diagnoses ON clinica.diagnoses;
|
||||
CREATE POLICY tenant_isolation_diagnoses ON clinica.diagnoses
|
||||
FOR ALL USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
|
||||
EXCEPTION WHEN undefined_object THEN NULL; END $$;
|
||||
|
||||
DO $$ BEGIN
|
||||
DROP POLICY IF EXISTS tenant_isolation_prescriptions ON clinica.prescriptions;
|
||||
CREATE POLICY tenant_isolation_prescriptions ON clinica.prescriptions
|
||||
FOR ALL USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
|
||||
EXCEPTION WHEN undefined_object THEN NULL; END $$;
|
||||
|
||||
DO $$ BEGIN
|
||||
DROP POLICY IF EXISTS tenant_isolation_prescription_items ON clinica.prescription_items;
|
||||
CREATE POLICY tenant_isolation_prescription_items ON clinica.prescription_items
|
||||
FOR ALL USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
|
||||
EXCEPTION WHEN undefined_object THEN NULL; END $$;
|
||||
|
||||
-- ============================================================================
|
||||
-- COMENTARIOS
|
||||
-- ============================================================================
|
||||
|
||||
COMMENT ON TABLE clinica.specialties IS 'Catálogo de especialidades médicas';
|
||||
COMMENT ON TABLE clinica.doctors IS 'Médicos y especialistas - extiende hr.employees';
|
||||
COMMENT ON TABLE clinica.patients IS 'Registro de pacientes - extiende core.partners';
|
||||
COMMENT ON TABLE clinica.patient_contacts IS 'Contactos de emergencia del paciente';
|
||||
COMMENT ON TABLE clinica.patient_insurance IS 'Información de seguros médicos';
|
||||
COMMENT ON TABLE clinica.appointment_slots IS 'Horarios disponibles por médico';
|
||||
COMMENT ON TABLE clinica.appointments IS 'Citas médicas programadas';
|
||||
COMMENT ON TABLE clinica.medical_records IS 'Expediente clínico electrónico (NOM-024-SSA3)';
|
||||
COMMENT ON TABLE clinica.consultations IS 'Consultas médicas realizadas';
|
||||
COMMENT ON TABLE clinica.vital_signs IS 'Signos vitales del paciente';
|
||||
COMMENT ON TABLE clinica.diagnoses IS 'Diagnósticos según CIE-10';
|
||||
COMMENT ON TABLE clinica.prescriptions IS 'Recetas médicas';
|
||||
COMMENT ON TABLE clinica.prescription_items IS 'Medicamentos en receta';
|
||||
|
||||
-- ============================================================================
|
||||
-- FIN TABLAS CLÍNICAS
|
||||
-- Total: 13 tablas, 4 ENUMs
|
||||
-- ============================================================================
|
||||
34
init/04-seed-data.sql
Normal file
34
init/04-seed-data.sql
Normal file
@ -0,0 +1,34 @@
|
||||
-- ============================================================================
|
||||
-- DATOS INICIALES - ERP Clínicas
|
||||
-- ============================================================================
|
||||
-- Versión: 1.0.0
|
||||
-- Fecha: 2025-12-09
|
||||
-- ============================================================================
|
||||
|
||||
-- Especialidades médicas comunes
|
||||
-- NOTA: Se insertan solo si el tenant existe (usar en script de inicialización)
|
||||
|
||||
/*
|
||||
-- Ejemplo de inserción (ejecutar con tenant_id específico):
|
||||
|
||||
INSERT INTO clinica.specialties (tenant_id, code, name, description, consultation_duration) VALUES
|
||||
('TENANT_UUID', 'MG', 'Medicina General', 'Atención médica primaria', 30),
|
||||
('TENANT_UUID', 'PED', 'Pediatría', 'Atención médica infantil', 30),
|
||||
('TENANT_UUID', 'GIN', 'Ginecología', 'Salud de la mujer', 30),
|
||||
('TENANT_UUID', 'CARD', 'Cardiología', 'Enfermedades del corazón', 45),
|
||||
('TENANT_UUID', 'DERM', 'Dermatología', 'Enfermedades de la piel', 30),
|
||||
('TENANT_UUID', 'OFT', 'Oftalmología', 'Salud visual', 30),
|
||||
('TENANT_UUID', 'ORL', 'Otorrinolaringología', 'Oído, nariz y garganta', 30),
|
||||
('TENANT_UUID', 'TRAU', 'Traumatología', 'Sistema músculo-esquelético', 30),
|
||||
('TENANT_UUID', 'NEUR', 'Neurología', 'Sistema nervioso', 45),
|
||||
('TENANT_UUID', 'PSIQ', 'Psiquiatría', 'Salud mental', 60),
|
||||
('TENANT_UUID', 'ENDO', 'Endocrinología', 'Sistema endocrino', 45),
|
||||
('TENANT_UUID', 'GAST', 'Gastroenterología', 'Sistema digestivo', 45),
|
||||
('TENANT_UUID', 'NEFR', 'Nefrología', 'Enfermedades renales', 45),
|
||||
('TENANT_UUID', 'UROL', 'Urología', 'Sistema urinario', 30),
|
||||
('TENANT_UUID', 'ONCO', 'Oncología', 'Tratamiento del cáncer', 60);
|
||||
*/
|
||||
|
||||
-- ============================================================================
|
||||
-- FIN SEED DATA
|
||||
-- ============================================================================
|
||||
446
schemas/01-clinica-core-schema-ddl.sql
Normal file
446
schemas/01-clinica-core-schema-ddl.sql
Normal file
@ -0,0 +1,446 @@
|
||||
-- ============================================================================
|
||||
-- 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
|
||||
-- ============================================================================
|
||||
148
schemas/04-financial-ext-schema-ddl.sql
Normal file
148
schemas/04-financial-ext-schema-ddl.sql
Normal file
@ -0,0 +1,148 @@
|
||||
-- ============================================================================
|
||||
-- FINANCIAL EXTENSIONS - FASE 8 ERP-Core
|
||||
-- ERP Clínicas (Base Genérica)
|
||||
-- ============================================================================
|
||||
-- Fecha: 2026-01-04
|
||||
-- Versión: 1.0
|
||||
-- ============================================================================
|
||||
|
||||
-- Schema
|
||||
CREATE SCHEMA IF NOT EXISTS financial;
|
||||
|
||||
-- ============================================================================
|
||||
-- ENUMS
|
||||
-- ============================================================================
|
||||
|
||||
DO $$ BEGIN
|
||||
CREATE TYPE financial.payment_method_type AS ENUM ('inbound', 'outbound');
|
||||
EXCEPTION WHEN duplicate_object THEN NULL;
|
||||
END $$;
|
||||
|
||||
DO $$ BEGIN
|
||||
CREATE TYPE financial.reconcile_model_type AS ENUM (
|
||||
'writeoff_button',
|
||||
'writeoff_suggestion',
|
||||
'invoice_matching'
|
||||
);
|
||||
EXCEPTION WHEN duplicate_object THEN NULL;
|
||||
END $$;
|
||||
|
||||
-- ============================================================================
|
||||
-- TABLAS
|
||||
-- ============================================================================
|
||||
|
||||
-- Líneas de términos de pago
|
||||
CREATE TABLE IF NOT EXISTS financial.payment_term_lines (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
tenant_id UUID NOT NULL,
|
||||
payment_term_id UUID,
|
||||
value_type VARCHAR(20) NOT NULL DEFAULT 'percent',
|
||||
value NUMERIC(10,2) DEFAULT 0,
|
||||
days INTEGER DEFAULT 0,
|
||||
day_of_month INTEGER,
|
||||
applies_to VARCHAR(50), -- 'consulta', 'procedimiento', 'laboratorio', 'farmacia'
|
||||
sequence INTEGER DEFAULT 10,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
COMMENT ON TABLE financial.payment_term_lines IS 'Líneas de términos de pago - FASE 8';
|
||||
COMMENT ON COLUMN financial.payment_term_lines.applies_to IS 'Tipo de servicio al que aplica';
|
||||
|
||||
-- Métodos de pago
|
||||
CREATE TABLE IF NOT EXISTS financial.payment_methods (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
tenant_id UUID NOT NULL,
|
||||
name VARCHAR(100) NOT NULL,
|
||||
code VARCHAR(20) NOT NULL,
|
||||
payment_type financial.payment_method_type NOT NULL,
|
||||
-- Extensiones clínica
|
||||
aplica_seguro BOOLEAN DEFAULT false,
|
||||
requiere_factura BOOLEAN DEFAULT false,
|
||||
porcentaje_seguro NUMERIC(5,2) DEFAULT 0,
|
||||
-- Control
|
||||
active BOOLEAN DEFAULT true,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
CONSTRAINT uq_payment_methods_tenant_code UNIQUE(tenant_id, code)
|
||||
);
|
||||
|
||||
COMMENT ON TABLE financial.payment_methods IS 'Métodos de pago - FASE 8';
|
||||
COMMENT ON COLUMN financial.payment_methods.aplica_seguro IS 'Si el método está asociado a pagos de seguro';
|
||||
COMMENT ON COLUMN financial.payment_methods.porcentaje_seguro IS 'Porcentaje que cubre el seguro';
|
||||
|
||||
-- Modelos de conciliación
|
||||
CREATE TABLE IF NOT EXISTS financial.reconcile_models (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
tenant_id UUID NOT NULL,
|
||||
name VARCHAR(100) NOT NULL,
|
||||
rule_type financial.reconcile_model_type NOT NULL DEFAULT 'writeoff_button',
|
||||
auto_reconcile BOOLEAN DEFAULT false,
|
||||
match_partner BOOLEAN DEFAULT true,
|
||||
match_amount BOOLEAN DEFAULT true,
|
||||
tolerance NUMERIC(10,2) DEFAULT 0,
|
||||
active BOOLEAN DEFAULT true,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
COMMENT ON TABLE financial.reconcile_models IS 'Modelos de conciliación automática - FASE 8';
|
||||
|
||||
-- Líneas de modelo de conciliación
|
||||
CREATE TABLE IF NOT EXISTS financial.reconcile_model_lines (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
tenant_id UUID NOT NULL,
|
||||
model_id UUID NOT NULL REFERENCES financial.reconcile_models(id) ON DELETE CASCADE,
|
||||
sequence INTEGER DEFAULT 10,
|
||||
account_id UUID,
|
||||
amount_type VARCHAR(20) DEFAULT 'percentage',
|
||||
amount_value NUMERIC(10,2) DEFAULT 100,
|
||||
label VARCHAR(100),
|
||||
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
COMMENT ON TABLE financial.reconcile_model_lines IS 'Líneas de modelo de conciliación - FASE 8';
|
||||
|
||||
-- ============================================================================
|
||||
-- ÍNDICES
|
||||
-- ============================================================================
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_payment_term_lines_tenant
|
||||
ON financial.payment_term_lines(tenant_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_payment_term_lines_payment_term
|
||||
ON financial.payment_term_lines(payment_term_id);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_payment_methods_tenant
|
||||
ON financial.payment_methods(tenant_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_payment_methods_code
|
||||
ON financial.payment_methods(tenant_id, code);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_reconcile_models_tenant
|
||||
ON financial.reconcile_models(tenant_id);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_reconcile_model_lines_model
|
||||
ON financial.reconcile_model_lines(model_id);
|
||||
|
||||
-- ============================================================================
|
||||
-- RLS
|
||||
-- ============================================================================
|
||||
|
||||
ALTER TABLE financial.payment_term_lines ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE financial.payment_methods ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE financial.reconcile_models ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
DROP POLICY IF EXISTS tenant_isolation_payment_term_lines ON financial.payment_term_lines;
|
||||
CREATE POLICY tenant_isolation_payment_term_lines ON financial.payment_term_lines
|
||||
USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
|
||||
|
||||
DROP POLICY IF EXISTS tenant_isolation_payment_methods ON financial.payment_methods;
|
||||
CREATE POLICY tenant_isolation_payment_methods ON financial.payment_methods
|
||||
USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
|
||||
|
||||
DROP POLICY IF EXISTS tenant_isolation_reconcile_models ON financial.reconcile_models;
|
||||
CREATE POLICY tenant_isolation_reconcile_models ON financial.reconcile_models
|
||||
USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
|
||||
|
||||
-- ============================================================================
|
||||
-- FIN FINANCIAL EXTENSIONS
|
||||
-- ============================================================================
|
||||
354
schemas/05-hr-ext-fase8-schema-ddl.sql
Normal file
354
schemas/05-hr-ext-fase8-schema-ddl.sql
Normal file
@ -0,0 +1,354 @@
|
||||
-- ============================================================================
|
||||
-- HR EXTENSIONS - FASE 8 ERP-Core
|
||||
-- ERP Clínicas (Base Genérica)
|
||||
-- ============================================================================
|
||||
-- Fecha: 2026-01-04
|
||||
-- Versión: 1.0
|
||||
-- ============================================================================
|
||||
|
||||
-- Schema
|
||||
CREATE SCHEMA IF NOT EXISTS hr;
|
||||
|
||||
-- ============================================================================
|
||||
-- ENUMS
|
||||
-- ============================================================================
|
||||
|
||||
DO $$ BEGIN
|
||||
CREATE TYPE hr.expense_status AS ENUM (
|
||||
'draft', 'submitted', 'approved', 'posted', 'paid', 'rejected'
|
||||
);
|
||||
EXCEPTION WHEN duplicate_object THEN NULL;
|
||||
END $$;
|
||||
|
||||
DO $$ BEGIN
|
||||
CREATE TYPE hr.resume_line_type AS ENUM (
|
||||
'experience', 'education', 'certification', 'internal'
|
||||
);
|
||||
EXCEPTION WHEN duplicate_object THEN NULL;
|
||||
END $$;
|
||||
|
||||
DO $$ BEGIN
|
||||
CREATE TYPE hr.payslip_status AS ENUM (
|
||||
'draft', 'verify', 'done', 'cancel'
|
||||
);
|
||||
EXCEPTION WHEN duplicate_object THEN NULL;
|
||||
END $$;
|
||||
|
||||
-- ============================================================================
|
||||
-- TABLAS
|
||||
-- ============================================================================
|
||||
|
||||
-- Ubicaciones de trabajo (consultorios/sucursales)
|
||||
CREATE TABLE IF NOT EXISTS hr.work_locations (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
tenant_id UUID NOT NULL,
|
||||
name VARCHAR(100) NOT NULL,
|
||||
address_id UUID,
|
||||
-- Extensiones clínica
|
||||
tipo_consultorio VARCHAR(50), -- 'general', 'especialidad', 'urgencias', 'quirofano', 'laboratorio'
|
||||
capacidad INTEGER DEFAULT 1,
|
||||
equipamiento TEXT[],
|
||||
horario_apertura TIME,
|
||||
horario_cierre TIME,
|
||||
-- Control
|
||||
active BOOLEAN DEFAULT true,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
COMMENT ON TABLE hr.work_locations IS 'Ubicaciones de trabajo/consultorios - FASE 8';
|
||||
COMMENT ON COLUMN hr.work_locations.tipo_consultorio IS 'Tipo de consultorio o área';
|
||||
|
||||
-- Tipos de habilidad
|
||||
CREATE TABLE IF NOT EXISTS hr.skill_types (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
tenant_id UUID NOT NULL,
|
||||
name VARCHAR(100) NOT NULL,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
COMMENT ON TABLE hr.skill_types IS 'Tipos de habilidad (Especialidad, Certificación, etc.) - FASE 8';
|
||||
|
||||
-- Habilidades/Especialidades
|
||||
CREATE TABLE IF NOT EXISTS hr.skills (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
tenant_id UUID NOT NULL,
|
||||
skill_type_id UUID NOT NULL REFERENCES hr.skill_types(id) ON DELETE CASCADE,
|
||||
name VARCHAR(100) NOT NULL,
|
||||
-- Extensiones clínica
|
||||
codigo_ssa VARCHAR(20),
|
||||
requiere_cedula BOOLEAN DEFAULT false,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
COMMENT ON TABLE hr.skills IS 'Habilidades/Especialidades médicas - FASE 8';
|
||||
COMMENT ON COLUMN hr.skills.codigo_ssa IS 'Código SSA de la especialidad';
|
||||
|
||||
-- Niveles de habilidad
|
||||
CREATE TABLE IF NOT EXISTS hr.skill_levels (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
tenant_id UUID NOT NULL,
|
||||
skill_type_id UUID NOT NULL REFERENCES hr.skill_types(id) ON DELETE CASCADE,
|
||||
name VARCHAR(100) NOT NULL,
|
||||
level INTEGER NOT NULL DEFAULT 1,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
COMMENT ON TABLE hr.skill_levels IS 'Niveles de habilidad - FASE 8';
|
||||
|
||||
-- Habilidades de empleado
|
||||
CREATE TABLE IF NOT EXISTS hr.employee_skills (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
tenant_id UUID NOT NULL,
|
||||
employee_id UUID NOT NULL,
|
||||
skill_id UUID NOT NULL REFERENCES hr.skills(id) ON DELETE CASCADE,
|
||||
skill_level_id UUID REFERENCES hr.skill_levels(id),
|
||||
-- Extensiones clínica
|
||||
cedula_profesional VARCHAR(20),
|
||||
fecha_certificacion DATE,
|
||||
fecha_vencimiento DATE,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
COMMENT ON TABLE hr.employee_skills IS 'Habilidades asignadas a empleados - FASE 8';
|
||||
COMMENT ON COLUMN hr.employee_skills.cedula_profesional IS 'Número de cédula profesional';
|
||||
|
||||
-- Hojas de gastos
|
||||
CREATE TABLE IF NOT EXISTS hr.expense_sheets (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
tenant_id UUID NOT NULL,
|
||||
employee_id UUID NOT NULL,
|
||||
name VARCHAR(100) NOT NULL,
|
||||
state hr.expense_status DEFAULT 'draft',
|
||||
accounting_date DATE,
|
||||
total_amount NUMERIC(12,2) DEFAULT 0,
|
||||
-- Extensiones clínica
|
||||
paciente_id UUID,
|
||||
cita_id UUID,
|
||||
centro_costo VARCHAR(50),
|
||||
-- Control
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
COMMENT ON TABLE hr.expense_sheets IS 'Hojas de gastos - FASE 8';
|
||||
COMMENT ON COLUMN hr.expense_sheets.paciente_id IS 'Paciente asociado al gasto (si aplica)';
|
||||
|
||||
-- Gastos individuales
|
||||
CREATE TABLE IF NOT EXISTS hr.expenses (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
tenant_id UUID NOT NULL,
|
||||
sheet_id UUID REFERENCES hr.expense_sheets(id) ON DELETE CASCADE,
|
||||
employee_id UUID NOT NULL,
|
||||
name VARCHAR(200) NOT NULL,
|
||||
date DATE NOT NULL DEFAULT CURRENT_DATE,
|
||||
product_id UUID,
|
||||
quantity NUMERIC(10,2) DEFAULT 1,
|
||||
unit_amount NUMERIC(12,2) NOT NULL,
|
||||
total_amount NUMERIC(12,2) NOT NULL,
|
||||
state hr.expense_status DEFAULT 'draft',
|
||||
reference VARCHAR(100),
|
||||
description TEXT,
|
||||
-- Control
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
COMMENT ON TABLE hr.expenses IS 'Gastos individuales - FASE 8';
|
||||
|
||||
-- Líneas de currículum
|
||||
CREATE TABLE IF NOT EXISTS hr.employee_resume_lines (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
tenant_id UUID NOT NULL,
|
||||
employee_id UUID NOT NULL,
|
||||
line_type hr.resume_line_type NOT NULL,
|
||||
name VARCHAR(200) NOT NULL,
|
||||
description TEXT,
|
||||
date_start DATE,
|
||||
date_end DATE,
|
||||
-- Control
|
||||
display_type VARCHAR(20) DEFAULT 'classic',
|
||||
sequence INTEGER DEFAULT 10,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
COMMENT ON TABLE hr.employee_resume_lines IS 'Líneas de currículum/experiencia - FASE 8';
|
||||
|
||||
-- Estructuras de nómina
|
||||
CREATE TABLE IF NOT EXISTS hr.payslip_structures (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
tenant_id UUID NOT NULL,
|
||||
name VARCHAR(100) NOT NULL,
|
||||
code VARCHAR(20) NOT NULL,
|
||||
-- Extensiones clínica
|
||||
tipo_pago VARCHAR(50), -- 'quincenal', 'semanal', 'honorarios', 'guardia'
|
||||
-- Control
|
||||
active BOOLEAN DEFAULT true,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
CONSTRAINT uq_payslip_structures_tenant_code UNIQUE(tenant_id, code)
|
||||
);
|
||||
|
||||
COMMENT ON TABLE hr.payslip_structures IS 'Estructuras de nómina - FASE 8';
|
||||
|
||||
-- Nóminas
|
||||
CREATE TABLE IF NOT EXISTS hr.payslips (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
tenant_id UUID NOT NULL,
|
||||
employee_id UUID NOT NULL,
|
||||
structure_id UUID REFERENCES hr.payslip_structures(id),
|
||||
name VARCHAR(100),
|
||||
number VARCHAR(50),
|
||||
date_from DATE NOT NULL,
|
||||
date_to DATE NOT NULL,
|
||||
state hr.payslip_status DEFAULT 'draft',
|
||||
-- Montos
|
||||
basic_wage NUMERIC(12,2) DEFAULT 0,
|
||||
gross NUMERIC(12,2) DEFAULT 0,
|
||||
net NUMERIC(12,2) DEFAULT 0,
|
||||
-- Extensiones clínica
|
||||
consultorio_id UUID REFERENCES hr.work_locations(id),
|
||||
-- Control
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
COMMENT ON TABLE hr.payslips IS 'Nóminas - FASE 8';
|
||||
|
||||
-- Líneas de nómina
|
||||
CREATE TABLE IF NOT EXISTS hr.payslip_lines (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
tenant_id UUID NOT NULL,
|
||||
payslip_id UUID NOT NULL REFERENCES hr.payslips(id) ON DELETE CASCADE,
|
||||
name VARCHAR(100) NOT NULL,
|
||||
code VARCHAR(20),
|
||||
category VARCHAR(50),
|
||||
sequence INTEGER DEFAULT 10,
|
||||
quantity NUMERIC(10,2) DEFAULT 1,
|
||||
rate NUMERIC(12,4) DEFAULT 0,
|
||||
amount NUMERIC(12,2) DEFAULT 0,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
COMMENT ON TABLE hr.payslip_lines IS 'Líneas de nómina - FASE 8';
|
||||
|
||||
-- ============================================================================
|
||||
-- CAMPOS ADICIONALES A EMPLOYEES (si existe)
|
||||
-- ============================================================================
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (SELECT 1 FROM information_schema.tables
|
||||
WHERE table_schema = 'hr' AND table_name = 'employees') THEN
|
||||
|
||||
-- work_location_id
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.columns
|
||||
WHERE table_schema = 'hr' AND table_name = 'employees'
|
||||
AND column_name = 'work_location_id') THEN
|
||||
ALTER TABLE hr.employees ADD COLUMN work_location_id UUID
|
||||
REFERENCES hr.work_locations(id);
|
||||
END IF;
|
||||
|
||||
-- badge_id
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.columns
|
||||
WHERE table_schema = 'hr' AND table_name = 'employees'
|
||||
AND column_name = 'badge_id') THEN
|
||||
ALTER TABLE hr.employees ADD COLUMN badge_id VARCHAR(50);
|
||||
END IF;
|
||||
|
||||
-- cedula_profesional
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.columns
|
||||
WHERE table_schema = 'hr' AND table_name = 'employees'
|
||||
AND column_name = 'cedula_profesional') THEN
|
||||
ALTER TABLE hr.employees ADD COLUMN cedula_profesional VARCHAR(20);
|
||||
END IF;
|
||||
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- ============================================================================
|
||||
-- ÍNDICES
|
||||
-- ============================================================================
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_work_locations_tenant ON hr.work_locations(tenant_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_work_locations_tipo ON hr.work_locations(tenant_id, tipo_consultorio);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_skill_types_tenant ON hr.skill_types(tenant_id);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_skills_tenant ON hr.skills(tenant_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_skills_type ON hr.skills(skill_type_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_skills_codigo ON hr.skills(codigo_ssa) WHERE codigo_ssa IS NOT NULL;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_skill_levels_tenant ON hr.skill_levels(tenant_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_skill_levels_type ON hr.skill_levels(skill_type_id);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_employee_skills_tenant ON hr.employee_skills(tenant_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_employee_skills_employee ON hr.employee_skills(employee_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_employee_skills_skill ON hr.employee_skills(skill_id);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_expense_sheets_tenant ON hr.expense_sheets(tenant_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_expense_sheets_employee ON hr.expense_sheets(employee_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_expense_sheets_state ON hr.expense_sheets(tenant_id, state);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_expenses_tenant ON hr.expenses(tenant_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_expenses_sheet ON hr.expenses(sheet_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_expenses_employee ON hr.expenses(employee_id);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_employee_resume_lines_tenant ON hr.employee_resume_lines(tenant_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_employee_resume_lines_employee ON hr.employee_resume_lines(employee_id);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_payslip_structures_tenant ON hr.payslip_structures(tenant_id);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_payslips_tenant ON hr.payslips(tenant_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_payslips_employee ON hr.payslips(employee_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_payslips_dates ON hr.payslips(tenant_id, date_from, date_to);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_payslip_lines_payslip ON hr.payslip_lines(payslip_id);
|
||||
|
||||
-- ============================================================================
|
||||
-- RLS
|
||||
-- ============================================================================
|
||||
|
||||
ALTER TABLE hr.work_locations ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE hr.skill_types ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE hr.skills ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE hr.skill_levels ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE hr.expense_sheets ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE hr.expenses ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE hr.payslip_structures ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE hr.payslips ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
DROP POLICY IF EXISTS tenant_isolation_work_locations ON hr.work_locations;
|
||||
CREATE POLICY tenant_isolation_work_locations ON hr.work_locations
|
||||
USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
|
||||
|
||||
DROP POLICY IF EXISTS tenant_isolation_skill_types ON hr.skill_types;
|
||||
CREATE POLICY tenant_isolation_skill_types ON hr.skill_types
|
||||
USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
|
||||
|
||||
DROP POLICY IF EXISTS tenant_isolation_skills ON hr.skills;
|
||||
CREATE POLICY tenant_isolation_skills ON hr.skills
|
||||
USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
|
||||
|
||||
DROP POLICY IF EXISTS tenant_isolation_skill_levels ON hr.skill_levels;
|
||||
CREATE POLICY tenant_isolation_skill_levels ON hr.skill_levels
|
||||
USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
|
||||
|
||||
DROP POLICY IF EXISTS tenant_isolation_expense_sheets ON hr.expense_sheets;
|
||||
CREATE POLICY tenant_isolation_expense_sheets ON hr.expense_sheets
|
||||
USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
|
||||
|
||||
DROP POLICY IF EXISTS tenant_isolation_expenses ON hr.expenses;
|
||||
CREATE POLICY tenant_isolation_expenses ON hr.expenses
|
||||
USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
|
||||
|
||||
DROP POLICY IF EXISTS tenant_isolation_payslip_structures ON hr.payslip_structures;
|
||||
CREATE POLICY tenant_isolation_payslip_structures ON hr.payslip_structures
|
||||
USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
|
||||
|
||||
DROP POLICY IF EXISTS tenant_isolation_payslips ON hr.payslips;
|
||||
CREATE POLICY tenant_isolation_payslips ON hr.payslips
|
||||
USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
|
||||
|
||||
-- ============================================================================
|
||||
-- FIN HR EXTENSIONS
|
||||
-- ============================================================================
|
||||
189
schemas/06-inventory-ext-fase8-schema-ddl.sql
Normal file
189
schemas/06-inventory-ext-fase8-schema-ddl.sql
Normal file
@ -0,0 +1,189 @@
|
||||
-- ============================================================================
|
||||
-- INVENTORY EXTENSIONS - FASE 8 ERP-Core
|
||||
-- ERP Clínicas (Base Genérica)
|
||||
-- ============================================================================
|
||||
-- Fecha: 2026-01-04
|
||||
-- Versión: 1.0
|
||||
-- ============================================================================
|
||||
|
||||
-- Schema
|
||||
CREATE SCHEMA IF NOT EXISTS inventory;
|
||||
|
||||
-- ============================================================================
|
||||
-- TABLAS
|
||||
-- ============================================================================
|
||||
|
||||
-- Tipos de paquete
|
||||
CREATE TABLE IF NOT EXISTS inventory.package_types (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
tenant_id UUID NOT NULL,
|
||||
name VARCHAR(100) NOT NULL,
|
||||
height NUMERIC(10,2),
|
||||
width NUMERIC(10,2),
|
||||
length NUMERIC(10,2),
|
||||
base_weight NUMERIC(10,2),
|
||||
max_weight NUMERIC(10,2),
|
||||
sequence INTEGER DEFAULT 10,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
COMMENT ON TABLE inventory.package_types IS 'Tipos de paquete - FASE 8';
|
||||
|
||||
-- Paquetes
|
||||
CREATE TABLE IF NOT EXISTS inventory.packages (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
tenant_id UUID NOT NULL,
|
||||
package_type_id UUID REFERENCES inventory.package_types(id),
|
||||
name VARCHAR(100),
|
||||
product_id UUID,
|
||||
-- Extensiones clínica (medicamentos)
|
||||
lote VARCHAR(50),
|
||||
fecha_fabricacion DATE,
|
||||
fecha_caducidad DATE,
|
||||
laboratorio VARCHAR(100),
|
||||
registro_sanitario VARCHAR(50),
|
||||
-- Control
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
COMMENT ON TABLE inventory.packages IS 'Paquetes/lotes de productos - FASE 8';
|
||||
COMMENT ON COLUMN inventory.packages.lote IS 'Número de lote del fabricante';
|
||||
COMMENT ON COLUMN inventory.packages.registro_sanitario IS 'Registro sanitario COFEPRIS';
|
||||
|
||||
-- Categorías de almacenamiento
|
||||
CREATE TABLE IF NOT EXISTS inventory.storage_categories (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
tenant_id UUID NOT NULL,
|
||||
name VARCHAR(100) NOT NULL,
|
||||
max_weight NUMERIC(10,2),
|
||||
allow_new_product VARCHAR(20) DEFAULT 'mixed',
|
||||
-- Extensiones clínica
|
||||
requiere_refrigeracion BOOLEAN DEFAULT false,
|
||||
temperatura_min NUMERIC(5,2),
|
||||
temperatura_max NUMERIC(5,2),
|
||||
es_controlado BOOLEAN DEFAULT false,
|
||||
requiere_receta BOOLEAN DEFAULT false,
|
||||
-- Control
|
||||
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
COMMENT ON TABLE inventory.storage_categories IS 'Categorías de almacenamiento - FASE 8';
|
||||
COMMENT ON COLUMN inventory.storage_categories.es_controlado IS 'Medicamento controlado (requiere receta especial)';
|
||||
COMMENT ON COLUMN inventory.storage_categories.requiere_refrigeracion IS 'Requiere cadena de frío';
|
||||
|
||||
-- Reglas de ubicación
|
||||
CREATE TABLE IF NOT EXISTS inventory.putaway_rules (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
tenant_id UUID NOT NULL,
|
||||
name VARCHAR(100),
|
||||
product_id UUID,
|
||||
category_id UUID REFERENCES inventory.storage_categories(id),
|
||||
warehouse_id UUID,
|
||||
location_in_id UUID,
|
||||
location_out_id UUID,
|
||||
sequence INTEGER DEFAULT 10,
|
||||
active BOOLEAN DEFAULT true,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
COMMENT ON TABLE inventory.putaway_rules IS 'Reglas de ubicación automática - FASE 8';
|
||||
|
||||
-- Estrategias de remoción
|
||||
CREATE TABLE IF NOT EXISTS inventory.removal_strategies (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
code VARCHAR(20) NOT NULL UNIQUE,
|
||||
name VARCHAR(100) NOT NULL,
|
||||
description TEXT,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
COMMENT ON TABLE inventory.removal_strategies IS 'Estrategias de remoción (FIFO, FEFO, etc.) - FASE 8';
|
||||
|
||||
-- ============================================================================
|
||||
-- CAMPOS ADICIONALES A PRODUCTS (si existe)
|
||||
-- ============================================================================
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (SELECT 1 FROM information_schema.tables
|
||||
WHERE table_schema = 'inventory' AND table_name = 'products') THEN
|
||||
|
||||
-- tracking
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.columns
|
||||
WHERE table_schema = 'inventory' AND table_name = 'products'
|
||||
AND column_name = 'tracking') THEN
|
||||
ALTER TABLE inventory.products ADD COLUMN tracking VARCHAR(20) DEFAULT 'none';
|
||||
END IF;
|
||||
|
||||
-- removal_strategy_id
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.columns
|
||||
WHERE table_schema = 'inventory' AND table_name = 'products'
|
||||
AND column_name = 'removal_strategy_id') THEN
|
||||
ALTER TABLE inventory.products ADD COLUMN removal_strategy_id UUID
|
||||
REFERENCES inventory.removal_strategies(id);
|
||||
END IF;
|
||||
|
||||
-- sale_ok
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.columns
|
||||
WHERE table_schema = 'inventory' AND table_name = 'products'
|
||||
AND column_name = 'sale_ok') THEN
|
||||
ALTER TABLE inventory.products ADD COLUMN sale_ok BOOLEAN DEFAULT true;
|
||||
END IF;
|
||||
|
||||
-- purchase_ok
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.columns
|
||||
WHERE table_schema = 'inventory' AND table_name = 'products'
|
||||
AND column_name = 'purchase_ok') THEN
|
||||
ALTER TABLE inventory.products ADD COLUMN purchase_ok BOOLEAN DEFAULT true;
|
||||
END IF;
|
||||
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- ============================================================================
|
||||
-- ÍNDICES
|
||||
-- ============================================================================
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_package_types_tenant ON inventory.package_types(tenant_id);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_packages_tenant ON inventory.packages(tenant_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_packages_type ON inventory.packages(package_type_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_packages_lote ON inventory.packages(tenant_id, lote);
|
||||
CREATE INDEX IF NOT EXISTS idx_packages_caducidad ON inventory.packages(tenant_id, fecha_caducidad);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_storage_categories_tenant ON inventory.storage_categories(tenant_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_storage_categories_controlado
|
||||
ON inventory.storage_categories(tenant_id, es_controlado) WHERE es_controlado = true;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_putaway_rules_tenant ON inventory.putaway_rules(tenant_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_putaway_rules_category ON inventory.putaway_rules(category_id);
|
||||
|
||||
-- ============================================================================
|
||||
-- RLS
|
||||
-- ============================================================================
|
||||
|
||||
ALTER TABLE inventory.package_types ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE inventory.packages ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE inventory.storage_categories ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE inventory.putaway_rules ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
DROP POLICY IF EXISTS tenant_isolation_package_types ON inventory.package_types;
|
||||
CREATE POLICY tenant_isolation_package_types ON inventory.package_types
|
||||
USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
|
||||
|
||||
DROP POLICY IF EXISTS tenant_isolation_packages ON inventory.packages;
|
||||
CREATE POLICY tenant_isolation_packages ON inventory.packages
|
||||
USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
|
||||
|
||||
DROP POLICY IF EXISTS tenant_isolation_storage_categories ON inventory.storage_categories;
|
||||
CREATE POLICY tenant_isolation_storage_categories ON inventory.storage_categories
|
||||
USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
|
||||
|
||||
DROP POLICY IF EXISTS tenant_isolation_putaway_rules ON inventory.putaway_rules;
|
||||
CREATE POLICY tenant_isolation_putaway_rules ON inventory.putaway_rules
|
||||
USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
|
||||
|
||||
-- ============================================================================
|
||||
-- FIN INVENTORY EXTENSIONS
|
||||
-- ============================================================================
|
||||
148
schemas/07-purchase-ext-fase8-schema-ddl.sql
Normal file
148
schemas/07-purchase-ext-fase8-schema-ddl.sql
Normal file
@ -0,0 +1,148 @@
|
||||
-- ============================================================================
|
||||
-- PURCHASE EXTENSIONS - FASE 8 ERP-Core
|
||||
-- ERP Clínicas (Base Genérica)
|
||||
-- ============================================================================
|
||||
-- Fecha: 2026-01-04
|
||||
-- Versión: 1.0
|
||||
-- ============================================================================
|
||||
|
||||
-- Schema
|
||||
CREATE SCHEMA IF NOT EXISTS purchase;
|
||||
|
||||
-- ============================================================================
|
||||
-- TABLAS
|
||||
-- ============================================================================
|
||||
|
||||
-- Información de proveedores por producto
|
||||
CREATE TABLE IF NOT EXISTS purchase.product_supplierinfo (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
tenant_id UUID NOT NULL,
|
||||
partner_id UUID,
|
||||
product_id UUID,
|
||||
product_name VARCHAR(200),
|
||||
product_code VARCHAR(50),
|
||||
-- Precios y cantidades
|
||||
min_qty NUMERIC(10,2) DEFAULT 1,
|
||||
price NUMERIC(12,4) NOT NULL,
|
||||
currency_id UUID,
|
||||
-- Tiempos
|
||||
delay INTEGER DEFAULT 1,
|
||||
date_start DATE,
|
||||
date_end DATE,
|
||||
-- Extensiones clínica
|
||||
aplica_iva BOOLEAN DEFAULT true,
|
||||
requiere_receta BOOLEAN DEFAULT false,
|
||||
tiempo_entrega_dias INTEGER DEFAULT 1,
|
||||
-- Control
|
||||
sequence INTEGER DEFAULT 10,
|
||||
active BOOLEAN DEFAULT true,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
COMMENT ON TABLE purchase.product_supplierinfo IS 'Información de productos por proveedor - FASE 8';
|
||||
COMMENT ON COLUMN purchase.product_supplierinfo.requiere_receta IS 'El producto requiere receta médica';
|
||||
COMMENT ON COLUMN purchase.product_supplierinfo.tiempo_entrega_dias IS 'Días estimados de entrega';
|
||||
|
||||
-- ============================================================================
|
||||
-- CAMPOS ADICIONALES A PURCHASE_ORDERS (si existe)
|
||||
-- ============================================================================
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (SELECT 1 FROM information_schema.tables
|
||||
WHERE table_schema = 'purchase' AND table_name = 'purchase_orders') THEN
|
||||
|
||||
-- origin
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.columns
|
||||
WHERE table_schema = 'purchase' AND table_name = 'purchase_orders'
|
||||
AND column_name = 'origin') THEN
|
||||
ALTER TABLE purchase.purchase_orders ADD COLUMN origin VARCHAR(100);
|
||||
END IF;
|
||||
|
||||
-- partner_ref
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.columns
|
||||
WHERE table_schema = 'purchase' AND table_name = 'purchase_orders'
|
||||
AND column_name = 'partner_ref') THEN
|
||||
ALTER TABLE purchase.purchase_orders ADD COLUMN partner_ref VARCHAR(100);
|
||||
END IF;
|
||||
|
||||
-- date_approve
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.columns
|
||||
WHERE table_schema = 'purchase' AND table_name = 'purchase_orders'
|
||||
AND column_name = 'date_approve') THEN
|
||||
ALTER TABLE purchase.purchase_orders ADD COLUMN date_approve TIMESTAMPTZ;
|
||||
END IF;
|
||||
|
||||
-- receipt_status
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.columns
|
||||
WHERE table_schema = 'purchase' AND table_name = 'purchase_orders'
|
||||
AND column_name = 'receipt_status') THEN
|
||||
ALTER TABLE purchase.purchase_orders ADD COLUMN receipt_status VARCHAR(20);
|
||||
END IF;
|
||||
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- ============================================================================
|
||||
-- FUNCIONES
|
||||
-- ============================================================================
|
||||
|
||||
-- Función para crear movimientos de stock
|
||||
CREATE OR REPLACE FUNCTION purchase.action_create_stock_moves(p_order_id UUID)
|
||||
RETURNS JSONB
|
||||
LANGUAGE plpgsql
|
||||
SECURITY DEFINER
|
||||
AS $$
|
||||
DECLARE
|
||||
v_result JSONB := '{"moves_created": 0, "errors": []}'::JSONB;
|
||||
v_move_count INTEGER := 0;
|
||||
BEGIN
|
||||
-- Verificar que la orden existe
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM purchase.purchase_orders
|
||||
WHERE id = p_order_id
|
||||
) THEN
|
||||
v_result := jsonb_set(v_result, '{errors}',
|
||||
v_result->'errors' || '["Orden de compra no encontrada"]'::JSONB);
|
||||
RETURN v_result;
|
||||
END IF;
|
||||
|
||||
-- Aquí se crearían los movimientos de stock
|
||||
-- La implementación depende de la estructura de inventory.stock_moves
|
||||
|
||||
v_result := jsonb_set(v_result, '{moves_created}', to_jsonb(v_move_count));
|
||||
v_result := jsonb_set(v_result, '{status}', '"success"'::JSONB);
|
||||
|
||||
RETURN v_result;
|
||||
END;
|
||||
$$;
|
||||
|
||||
COMMENT ON FUNCTION purchase.action_create_stock_moves IS 'Crea movimientos de stock desde orden de compra - FASE 8';
|
||||
|
||||
-- ============================================================================
|
||||
-- ÍNDICES
|
||||
-- ============================================================================
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_product_supplierinfo_tenant
|
||||
ON purchase.product_supplierinfo(tenant_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_product_supplierinfo_partner
|
||||
ON purchase.product_supplierinfo(partner_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_product_supplierinfo_product
|
||||
ON purchase.product_supplierinfo(product_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_product_supplierinfo_dates
|
||||
ON purchase.product_supplierinfo(tenant_id, date_start, date_end);
|
||||
|
||||
-- ============================================================================
|
||||
-- RLS
|
||||
-- ============================================================================
|
||||
|
||||
ALTER TABLE purchase.product_supplierinfo ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
DROP POLICY IF EXISTS tenant_isolation_supplierinfo ON purchase.product_supplierinfo;
|
||||
CREATE POLICY tenant_isolation_supplierinfo ON purchase.product_supplierinfo
|
||||
USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
|
||||
|
||||
-- ============================================================================
|
||||
-- FIN PURCHASE EXTENSIONS
|
||||
-- ============================================================================
|
||||
151
schemas/08-clinica-ext-fase8-schema-ddl.sql
Normal file
151
schemas/08-clinica-ext-fase8-schema-ddl.sql
Normal file
@ -0,0 +1,151 @@
|
||||
-- ============================================================================
|
||||
-- CLINICA EXTENSIONS - FASE 8 ERP-Core
|
||||
-- ERP Clínicas (Base Genérica)
|
||||
-- ============================================================================
|
||||
-- Fecha: 2026-01-04
|
||||
-- Versión: 1.0
|
||||
-- ============================================================================
|
||||
|
||||
-- El schema clinica ya existe (03-clinical-tables.sql)
|
||||
|
||||
-- ============================================================================
|
||||
-- TABLAS
|
||||
-- ============================================================================
|
||||
|
||||
-- Personal de clínica (adaptación de collaborators)
|
||||
CREATE TABLE IF NOT EXISTS clinica.personal_clinica (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
tenant_id UUID NOT NULL,
|
||||
employee_id UUID NOT NULL,
|
||||
consultorio_id UUID,
|
||||
-- Datos del rol
|
||||
rol VARCHAR(50) NOT NULL, -- 'medico', 'enfermera', 'recepcion', 'auxiliar', 'laboratorio', 'farmacia'
|
||||
vigencia_desde DATE,
|
||||
vigencia_hasta DATE,
|
||||
es_titular BOOLEAN DEFAULT false,
|
||||
horario JSONB, -- {"lunes": {"inicio": "09:00", "fin": "18:00"}, ...}
|
||||
-- Control
|
||||
active BOOLEAN DEFAULT true,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
COMMENT ON TABLE clinica.personal_clinica IS 'Personal asignado a consultorios - FASE 8';
|
||||
COMMENT ON COLUMN clinica.personal_clinica.rol IS 'Rol del personal en el consultorio';
|
||||
COMMENT ON COLUMN clinica.personal_clinica.horario IS 'Horario de trabajo en formato JSON';
|
||||
|
||||
-- Calificaciones/Ratings
|
||||
CREATE TABLE IF NOT EXISTS clinica.ratings (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
tenant_id UUID NOT NULL,
|
||||
consultation_id UUID,
|
||||
patient_id UUID,
|
||||
doctor_id UUID,
|
||||
-- Calificación
|
||||
rating INTEGER NOT NULL CHECK (rating BETWEEN 1 AND 5),
|
||||
feedback TEXT,
|
||||
-- Aspectos específicos
|
||||
puntualidad INTEGER CHECK (puntualidad BETWEEN 1 AND 5),
|
||||
atencion INTEGER CHECK (atencion BETWEEN 1 AND 5),
|
||||
instalaciones INTEGER CHECK (instalaciones BETWEEN 1 AND 5),
|
||||
-- Metadata
|
||||
rated_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
is_anonymous BOOLEAN DEFAULT false,
|
||||
-- Control
|
||||
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
COMMENT ON TABLE clinica.ratings IS 'Calificaciones de consultas - FASE 8';
|
||||
COMMENT ON COLUMN clinica.ratings.rating IS 'Calificación general de 1 a 5';
|
||||
|
||||
-- ============================================================================
|
||||
-- FKs OPCIONALES
|
||||
-- ============================================================================
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
-- FK personal_clinica → hr.work_locations
|
||||
IF EXISTS (SELECT 1 FROM information_schema.tables
|
||||
WHERE table_schema = 'hr' AND table_name = 'work_locations') THEN
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.table_constraints
|
||||
WHERE constraint_name = 'fk_personal_consultorio') THEN
|
||||
ALTER TABLE clinica.personal_clinica
|
||||
ADD CONSTRAINT fk_personal_consultorio
|
||||
FOREIGN KEY (consultorio_id) REFERENCES hr.work_locations(id) ON DELETE SET NULL;
|
||||
END IF;
|
||||
END IF;
|
||||
|
||||
-- FK ratings → clinica.consultations
|
||||
IF EXISTS (SELECT 1 FROM information_schema.tables
|
||||
WHERE table_schema = 'clinica' AND table_name = 'consultations') THEN
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.table_constraints
|
||||
WHERE constraint_name = 'fk_rating_consultation') THEN
|
||||
ALTER TABLE clinica.ratings
|
||||
ADD CONSTRAINT fk_rating_consultation
|
||||
FOREIGN KEY (consultation_id) REFERENCES clinica.consultations(id) ON DELETE SET NULL;
|
||||
END IF;
|
||||
END IF;
|
||||
|
||||
-- FK ratings → clinica.patients
|
||||
IF EXISTS (SELECT 1 FROM information_schema.tables
|
||||
WHERE table_schema = 'clinica' AND table_name = 'patients') THEN
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.table_constraints
|
||||
WHERE constraint_name = 'fk_rating_patient') THEN
|
||||
ALTER TABLE clinica.ratings
|
||||
ADD CONSTRAINT fk_rating_patient
|
||||
FOREIGN KEY (patient_id) REFERENCES clinica.patients(id) ON DELETE SET NULL;
|
||||
END IF;
|
||||
END IF;
|
||||
|
||||
-- FK ratings → clinica.doctors
|
||||
IF EXISTS (SELECT 1 FROM information_schema.tables
|
||||
WHERE table_schema = 'clinica' AND table_name = 'doctors') THEN
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.table_constraints
|
||||
WHERE constraint_name = 'fk_rating_doctor') THEN
|
||||
ALTER TABLE clinica.ratings
|
||||
ADD CONSTRAINT fk_rating_doctor
|
||||
FOREIGN KEY (doctor_id) REFERENCES clinica.doctors(id) ON DELETE SET NULL;
|
||||
END IF;
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- ============================================================================
|
||||
-- ÍNDICES
|
||||
-- ============================================================================
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_personal_clinica_tenant
|
||||
ON clinica.personal_clinica(tenant_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_personal_clinica_employee
|
||||
ON clinica.personal_clinica(employee_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_personal_clinica_consultorio
|
||||
ON clinica.personal_clinica(consultorio_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_personal_clinica_rol
|
||||
ON clinica.personal_clinica(tenant_id, rol);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_ratings_tenant
|
||||
ON clinica.ratings(tenant_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_ratings_consultation
|
||||
ON clinica.ratings(consultation_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_ratings_doctor
|
||||
ON clinica.ratings(doctor_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_ratings_patient
|
||||
ON clinica.ratings(patient_id);
|
||||
|
||||
-- ============================================================================
|
||||
-- RLS
|
||||
-- ============================================================================
|
||||
|
||||
ALTER TABLE clinica.personal_clinica ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE clinica.ratings ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
DROP POLICY IF EXISTS tenant_isolation_personal_clinica ON clinica.personal_clinica;
|
||||
CREATE POLICY tenant_isolation_personal_clinica ON clinica.personal_clinica
|
||||
USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
|
||||
|
||||
DROP POLICY IF EXISTS tenant_isolation_ratings ON clinica.ratings;
|
||||
CREATE POLICY tenant_isolation_ratings ON clinica.ratings
|
||||
USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
|
||||
|
||||
-- ============================================================================
|
||||
-- FIN CLINICA EXTENSIONS
|
||||
-- ============================================================================
|
||||
11
seeds/fase8/00-removal-strategies.sql
Normal file
11
seeds/fase8/00-removal-strategies.sql
Normal file
@ -0,0 +1,11 @@
|
||||
-- ============================================================================
|
||||
-- SEED DATA: Estrategias de Remoción
|
||||
-- FASE-8 ERP-Core - ERP Clínicas
|
||||
-- ============================================================================
|
||||
|
||||
INSERT INTO inventory.removal_strategies (code, name, description) VALUES
|
||||
('fifo', 'First In First Out', 'Salida por fecha de entrada más antigua'),
|
||||
('lifo', 'Last In First Out', 'Salida por fecha de entrada más reciente'),
|
||||
('fefo', 'First Expired First Out', 'Salida por fecha de caducidad más próxima - RECOMENDADO para medicamentos'),
|
||||
('closest', 'Closest Location', 'Salida por ubicación más cercana')
|
||||
ON CONFLICT (code) DO NOTHING;
|
||||
103
seeds/fase8/01-clinica-skills.sql
Normal file
103
seeds/fase8/01-clinica-skills.sql
Normal file
@ -0,0 +1,103 @@
|
||||
-- ============================================================================
|
||||
-- SEED DATA: Habilidades/Especialidades Médicas
|
||||
-- FASE-8 ERP-Core - ERP Clínicas
|
||||
-- ============================================================================
|
||||
-- NOTA: Ejecutar después de SET app.current_tenant_id = 'UUID-DEL-TENANT';
|
||||
-- ============================================================================
|
||||
|
||||
-- Tipos de habilidad médica
|
||||
INSERT INTO hr.skill_types (tenant_id, name)
|
||||
SELECT current_setting('app.current_tenant_id', true)::UUID, name
|
||||
FROM (VALUES
|
||||
('Especialidad Médica'),
|
||||
('Subespecialidad'),
|
||||
('Certificación'),
|
||||
('Curso/Diplomado'),
|
||||
('Idioma')
|
||||
) AS t(name)
|
||||
WHERE current_setting('app.current_tenant_id', true) IS NOT NULL
|
||||
AND current_setting('app.current_tenant_id', true) != ''
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- Niveles de habilidad (para cada tipo)
|
||||
INSERT INTO hr.skill_levels (tenant_id, skill_type_id, name, level)
|
||||
SELECT
|
||||
current_setting('app.current_tenant_id', true)::UUID,
|
||||
st.id,
|
||||
l.name,
|
||||
l.level
|
||||
FROM hr.skill_types st
|
||||
CROSS JOIN (VALUES
|
||||
('Residente', 1),
|
||||
('Especialista', 2),
|
||||
('Subespecialista', 3),
|
||||
('Fellow', 4)
|
||||
) AS l(name, level)
|
||||
WHERE st.tenant_id = current_setting('app.current_tenant_id', true)::UUID
|
||||
AND st.name IN ('Especialidad Médica', 'Subespecialidad')
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- Niveles para certificaciones
|
||||
INSERT INTO hr.skill_levels (tenant_id, skill_type_id, name, level)
|
||||
SELECT
|
||||
current_setting('app.current_tenant_id', true)::UUID,
|
||||
st.id,
|
||||
l.name,
|
||||
l.level
|
||||
FROM hr.skill_types st
|
||||
CROSS JOIN (VALUES
|
||||
('En trámite', 1),
|
||||
('Vigente', 2),
|
||||
('Recertificado', 3)
|
||||
) AS l(name, level)
|
||||
WHERE st.tenant_id = current_setting('app.current_tenant_id', true)::UUID
|
||||
AND st.name = 'Certificación'
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- Especialidades médicas comunes
|
||||
INSERT INTO hr.skills (tenant_id, skill_type_id, name, codigo_ssa, requiere_cedula)
|
||||
SELECT
|
||||
current_setting('app.current_tenant_id', true)::UUID,
|
||||
id,
|
||||
unnest(ARRAY[
|
||||
'Medicina General',
|
||||
'Medicina Familiar',
|
||||
'Pediatría',
|
||||
'Ginecología y Obstetricia',
|
||||
'Medicina Interna',
|
||||
'Cardiología',
|
||||
'Dermatología',
|
||||
'Oftalmología',
|
||||
'Otorrinolaringología',
|
||||
'Traumatología y Ortopedia',
|
||||
'Neurología',
|
||||
'Psiquiatría',
|
||||
'Urología',
|
||||
'Gastroenterología',
|
||||
'Neumología'
|
||||
]),
|
||||
NULL,
|
||||
true
|
||||
FROM hr.skill_types
|
||||
WHERE name = 'Especialidad Médica'
|
||||
AND tenant_id = current_setting('app.current_tenant_id', true)::UUID
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- Certificaciones comunes
|
||||
INSERT INTO hr.skills (tenant_id, skill_type_id, name, requiere_cedula)
|
||||
SELECT
|
||||
current_setting('app.current_tenant_id', true)::UUID,
|
||||
id,
|
||||
unnest(ARRAY[
|
||||
'Consejo de Especialidad',
|
||||
'COFEPRIS',
|
||||
'BLS (Basic Life Support)',
|
||||
'ACLS (Advanced Cardiac Life Support)',
|
||||
'PALS (Pediatric Advanced Life Support)',
|
||||
'NOM-024-SSA3 Expediente Clínico'
|
||||
]),
|
||||
false
|
||||
FROM hr.skill_types
|
||||
WHERE name = 'Certificación'
|
||||
AND tenant_id = current_setting('app.current_tenant_id', true)::UUID
|
||||
ON CONFLICT DO NOTHING;
|
||||
83
seeds/fase8/02-clinica-catalogos.sql
Normal file
83
seeds/fase8/02-clinica-catalogos.sql
Normal file
@ -0,0 +1,83 @@
|
||||
-- ============================================================================
|
||||
-- SEED DATA: Catálogos de Clínica
|
||||
-- FASE-8 ERP-Core - ERP Clínicas
|
||||
-- ============================================================================
|
||||
-- NOTA: Ejecutar después de SET app.current_tenant_id = 'UUID-DEL-TENANT';
|
||||
-- ============================================================================
|
||||
|
||||
-- Categorías de almacén para clínicas
|
||||
INSERT INTO inventory.storage_categories (tenant_id, name, max_weight, allow_new_product,
|
||||
requiere_refrigeracion, temperatura_min, temperatura_max, es_controlado, requiere_receta)
|
||||
SELECT current_setting('app.current_tenant_id', true)::UUID, name, max_weight, allow_new_product,
|
||||
requiere_refrigeracion, temperatura_min, temperatura_max, es_controlado, requiere_receta
|
||||
FROM (VALUES
|
||||
('Farmacia General', 5000.0, 'mixed', false, NULL, NULL, false, false),
|
||||
('Refrigerados', 500.0, 'same', true, 2.0, 8.0, false, true),
|
||||
('Medicamentos Controlados', 100.0, 'same', false, NULL, NULL, true, true),
|
||||
('Material Quirúrgico', 1000.0, 'mixed', false, NULL, NULL, false, false),
|
||||
('Insumos Laboratorio', 500.0, 'mixed', false, NULL, NULL, false, false),
|
||||
('Vacunas', 200.0, 'same', true, 2.0, 8.0, false, true)
|
||||
) AS t(name, max_weight, allow_new_product, requiere_refrigeracion, temperatura_min, temperatura_max, es_controlado, requiere_receta)
|
||||
WHERE current_setting('app.current_tenant_id', true) IS NOT NULL
|
||||
AND current_setting('app.current_tenant_id', true) != ''
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- Tipos de paquete para medicamentos
|
||||
INSERT INTO inventory.package_types (tenant_id, name, height, width, length, base_weight, max_weight, sequence)
|
||||
SELECT current_setting('app.current_tenant_id', true)::UUID, name, height, width, length, base_weight, max_weight, seq
|
||||
FROM (VALUES
|
||||
('Caja Medicamentos', 20.0, 15.0, 10.0, 0.1, 2.0, 10),
|
||||
('Blister', 15.0, 10.0, 1.0, 0.01, 0.1, 20),
|
||||
('Frasco', 10.0, 5.0, 5.0, 0.05, 0.5, 30),
|
||||
('Ampolleta', 8.0, 2.0, 2.0, 0.01, 0.05, 40),
|
||||
('Bolsa Suero', 30.0, 20.0, 5.0, 0.1, 1.0, 50),
|
||||
('Jeringa', 15.0, 3.0, 3.0, 0.02, 0.1, 60)
|
||||
) AS t(name, height, width, length, base_weight, max_weight, seq)
|
||||
WHERE current_setting('app.current_tenant_id', true) IS NOT NULL
|
||||
AND current_setting('app.current_tenant_id', true) != ''
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- Métodos de pago para clínicas
|
||||
INSERT INTO financial.payment_methods (tenant_id, name, code, payment_type, aplica_seguro, requiere_factura, porcentaje_seguro)
|
||||
SELECT current_setting('app.current_tenant_id', true)::UUID, name, code, payment_type::financial.payment_method_type, aplica_seguro, requiere_factura, porcentaje_seguro
|
||||
FROM (VALUES
|
||||
('Efectivo', 'efectivo', 'inbound', false, false, 0),
|
||||
('Tarjeta Débito', 'td', 'inbound', false, false, 0),
|
||||
('Tarjeta Crédito', 'tc', 'inbound', false, true, 0),
|
||||
('Transferencia', 'transfer', 'inbound', false, true, 0),
|
||||
('Seguro GMM', 'seguro_gmm', 'inbound', true, true, 80),
|
||||
('Seguro GMA', 'seguro_gma', 'inbound', true, true, 70),
|
||||
('Convenio Empresa', 'convenio', 'inbound', false, true, 0)
|
||||
) AS t(name, code, payment_type, aplica_seguro, requiere_factura, porcentaje_seguro)
|
||||
WHERE current_setting('app.current_tenant_id', true) IS NOT NULL
|
||||
AND current_setting('app.current_tenant_id', true) != ''
|
||||
ON CONFLICT (tenant_id, code) DO NOTHING;
|
||||
|
||||
-- Estructuras de nómina para clínicas
|
||||
INSERT INTO hr.payslip_structures (tenant_id, name, code, tipo_pago)
|
||||
SELECT current_setting('app.current_tenant_id', true)::UUID, name, code, tipo_pago
|
||||
FROM (VALUES
|
||||
('Nómina Quincenal', 'NOM-QUIN', 'quincenal'),
|
||||
('Nómina Semanal', 'NOM-SEM', 'semanal'),
|
||||
('Honorarios Médicos', 'HON-MED', 'honorarios'),
|
||||
('Pago por Guardia', 'PAG-GUAR', 'guardia'),
|
||||
('Comisión por Consulta', 'COM-CONS', 'comision')
|
||||
) AS t(name, code, tipo_pago)
|
||||
WHERE current_setting('app.current_tenant_id', true) IS NOT NULL
|
||||
AND current_setting('app.current_tenant_id', true) != ''
|
||||
ON CONFLICT (tenant_id, code) DO NOTHING;
|
||||
|
||||
-- Tipos de consultorio
|
||||
INSERT INTO hr.work_locations (tenant_id, name, tipo_consultorio, capacidad)
|
||||
SELECT current_setting('app.current_tenant_id', true)::UUID, name, tipo, capacidad
|
||||
FROM (VALUES
|
||||
('Consultorio 1', 'general', 1),
|
||||
('Consultorio 2', 'general', 1),
|
||||
('Consultorio Especialidad', 'especialidad', 1),
|
||||
('Área de Urgencias', 'urgencias', 3),
|
||||
('Laboratorio', 'laboratorio', 5),
|
||||
('Farmacia', 'farmacia', 2)
|
||||
) AS t(name, tipo, capacidad)
|
||||
WHERE current_setting('app.current_tenant_id', true) IS NOT NULL
|
||||
AND current_setting('app.current_tenant_id', true) != ''
|
||||
ON CONFLICT DO NOTHING;
|
||||
Loading…
Reference in New Issue
Block a user