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