From 431e1273b8cbebeb5a4bb47d7ab7d734dd6fa7cf Mon Sep 17 00:00:00 2001 From: rckrdmrd Date: Fri, 16 Jan 2026 08:12:04 -0600 Subject: [PATCH] =?UTF-8?q?Migraci=C3=B3n=20desde=20erp-vidrio-templado/da?= =?UTF-8?q?tabase=20-=20Est=C3=A1ndar=20multi-repo=20v2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.5 --- .gitignore | 4 + HERENCIA-ERP-CORE.md | 191 +++++++ README.md | 91 ++- init/00-extensions.sql | 23 + init/01-create-schemas.sql | 15 + init/02-rls-functions.sql | 30 + init/03-vidrio-tables.sql | 716 ++++++++++++++++++++++++ schemas/04-financial-ext-schema-ddl.sql | 53 ++ schemas/05-hr-ext-fase8-schema-ddl.sql | 181 ++++++ seeds/fase8/01-vidrio-catalogos.sql | 50 ++ 10 files changed, 1352 insertions(+), 2 deletions(-) create mode 100644 .gitignore create mode 100644 HERENCIA-ERP-CORE.md create mode 100644 init/00-extensions.sql create mode 100644 init/01-create-schemas.sql create mode 100644 init/02-rls-functions.sql create mode 100644 init/03-vidrio-tables.sql create mode 100644 schemas/04-financial-ext-schema-ddl.sql create mode 100644 schemas/05-hr-ext-fase8-schema-ddl.sql create mode 100644 seeds/fase8/01-vidrio-catalogos.sql diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1900a45 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +node_modules/ +dist/ +coverage/ +.env diff --git a/HERENCIA-ERP-CORE.md b/HERENCIA-ERP-CORE.md new file mode 100644 index 0000000..0ca23fe --- /dev/null +++ b/HERENCIA-ERP-CORE.md @@ -0,0 +1,191 @@ +# Herencia de Base de Datos - ERP Core -> Vidrio Templado + +**Fecha:** 2025-12-08 +**Versión:** 1.0 +**Vertical:** Vidrio Templado +**Nivel:** 2B.2 + +--- + +## RESUMEN + +La vertical de Vidrio Templado hereda los schemas base del ERP Core y extiende con schemas específicos del dominio de producción de vidrio. + +**Ubicación DDL Core:** `apps/erp-core/database/ddl/` + +--- + +## ARQUITECTURA DE HERENCIA + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ ERP CORE (Base) │ +│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ +│ │ auth │ │ core │ │financial│ │inventory│ │ purchase │ │ +│ │ 26 tbl │ │ 12 tbl │ │ 15 tbl │ │ 15 tbl │ │ 8 tbl │ │ +│ └─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │ +│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ +│ │ sales │ │analytics│ │ system │ │ +│ │ 6 tbl │ │ 5 tbl │ │ 10 tbl │ │ +│ └─────────┘ └─────────┘ └─────────┘ │ +│ TOTAL: ~97 tablas heredadas │ +└─────────────────────────────────────────────────────────────────┘ + │ + │ HEREDA + ▼ +┌─────────────────────────────────────────────────────────────────┐ +│ VIDRIO TEMPLADO (Extensiones) │ +│ ┌───────────────┐ ┌───────────────┐ ┌───────────────┐ │ +│ │ production │ │ quality │ │ glass │ │ +│ │ management │ │ control │ │ inventory │ │ +│ │ (hornos) │ │ (inspección) │ │ (lotes) │ │ +│ └───────────────┘ └───────────────┘ └───────────────┘ │ +│ EXTENSIONES: ~25 tablas (planificadas) │ +└─────────────────────────────────────────────────────────────────┘ +``` + +--- + +## SCHEMAS HEREDADOS DEL CORE + +| Schema | Tablas | Uso en Vidrio Templado | +|--------|--------|------------------------| +| `auth` | 26 | Autenticación, usuarios, roles, permisos | +| `core` | 12 | Partners (clientes), catálogos | +| `financial` | 15 | Facturas, cuentas contables | +| `inventory` | 15 | Base para materia prima y producto terminado | +| `purchase` | 8 | Compras de materiales | +| `sales` | 6 | Cotizaciones, órdenes de venta | +| `analytics` | 5 | Centros de costo | +| `system` | 10 | Mensajes, notificaciones | + +**Total heredado:** ~97 tablas + +--- + +## SCHEMAS ESPECÍFICOS DE VIDRIO TEMPLADO (Planificados) + +### 1. Schema `production` (estimado 10+ tablas) + +**Propósito:** Gestión de producción y hornos de templado + +```sql +-- Tablas principales planificadas: +production.production_orders -- Órdenes de producción +production.production_lines -- Líneas de producción (hornos) +production.work_orders -- Órdenes de trabajo +production.cutting_plans -- Planes de corte +production.oven_schedules -- Programación de hornos +production.temperature_logs -- Registros de temperatura +``` + +### 2. Schema `quality` (estimado 8+ tablas) + +**Propósito:** Control de calidad y trazabilidad + +```sql +-- Tablas principales planificadas: +quality.inspections -- Inspecciones de calidad +quality.defect_types -- Catálogo de defectos +quality.quality_tests -- Pruebas de calidad +quality.certifications -- Certificaciones de producto +quality.non_conformities -- No conformidades +``` + +### 3. Schema `glass` (estimado 7+ tablas) + +**Propósito:** Inventario especializado de vidrio + +```sql +-- Extiende: inventory schema del core +glass.glass_types -- Tipos de vidrio +glass.glass_lots -- Lotes de producción +glass.glass_dimensions -- Dimensiones estándar +glass.raw_materials -- Materia prima +``` + +--- + +## 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 Vidrio Templado | SP | Estado | +|-----------|------------------------------|----:|--------| +| SPEC-SISTEMA-SECUENCIAS | Foliado de órdenes y lotes | 8 | ✅ DDL LISTO | +| SPEC-VALORACION-INVENTARIO | Costeo de materia prima y producto | 21 | ✅ DDL LISTO | +| SPEC-SEGURIDAD-API-KEYS-PERMISOS | Control de acceso | 31 | ✅ DDL LISTO | +| SPEC-TRAZABILIDAD-LOTES-SERIES | Lotes de producción de vidrio | 13 | ✅ DDL LISTO | +| SPEC-PRICING-RULES | Precios por dimensiones y tipo | 8 | PENDIENTE | +| SPEC-PROYECTOS-DEPENDENCIAS-BURNDOWN | Control de producción | 13 | PENDIENTE | +| SPEC-MAIL-THREAD-TRACKING | Historial de órdenes | 13 | PENDIENTE | +| SPEC-WIZARD-TRANSIENT-MODEL | Wizards de corte y templado | 8 | PENDIENTE | + +### SPECS Opcionales + +| Spec Core | Decisión | Razón | +|-----------|----------|-------| +| SPEC-INVENTARIOS-CICLICOS | EVALUAR | Útil para materia prima | +| SPEC-FIRMA-ELECTRONICA-NOM151 | EVALUAR | Certificados de calidad | + +### SPECS No Aplican + +| Spec Core | Razón | +|-----------|-------| +| SPEC-INTEGRACION-CALENDAR | No requiere calendario externo | +| SPEC-CONSOLIDACION-FINANCIERA | Negocio de una sola planta | + +--- + +## 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 Vidrio Templado +cd apps/verticales/vidrio-templado/database +psql $DATABASE_URL -f init/00-extensions.sql +psql $DATABASE_URL -f init/01-create-schemas.sql +psql $DATABASE_URL -f init/02-production-tables.sql +psql $DATABASE_URL -f init/03-quality-tables.sql +psql $DATABASE_URL -f init/04-glass-inventory.sql +``` + +--- + +## MAPEO DE NOMENCLATURA + +| Core | Vidrio Templado | +|------|-----------------| +| `core.partners` | Clientes, proveedores | +| `inventory.products` | Producto terminado base | +| `inventory.locations` | Almacenes de vidrio | +| `sales.sale_orders` | Pedidos de vidrio | +| `purchase.purchase_orders` | Compras de materia prima | + +--- + +## 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 diff --git a/README.md b/README.md index 3ec5118..7631956 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,90 @@ -# erp-vidrio-templado-database-v2 +# Base de Datos - ERP Vidrio Templado -Database de erp-vidrio-templado - Workspace V2 \ No newline at end of file +## Resumen + +| Aspecto | Valor | +|---------|-------| +| **Schema principal** | `vidrio` | +| **Tablas específicas** | 14 | +| **ENUMs** | 5 | +| **Hereda de ERP-Core** | 144 tablas (12 schemas) | + +## Prerequisitos + +1. **ERP-Core instalado** con todos sus schemas +2. **Extensiones PostgreSQL**: pg_trgm + +## Orden de Ejecución DDL + +```bash +# 1. Instalar ERP-Core primero +cd apps/erp-core/database +./scripts/reset-database.sh + +# 2. Instalar extensión Vidrio Templado +cd apps/verticales/vidrio-templado/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-vidrio-tables.sql +``` + +## Tablas Implementadas + +### Schema: vidrio (14 tablas) + +| Tabla | Módulo | Descripción | +|-------|--------|-------------| +| glass_catalog | VT-001 | Catálogo de tipos de vidrio | +| process_catalog | VT-001 | Catálogo de procesos | +| production_orders | VT-002 | Órdenes de producción | +| production_lines | VT-002 | Líneas de producción | +| furnaces | VT-003 | Hornos de templado | +| furnace_batches | VT-003 | Lotes en horno | +| cutting_machines | VT-003 | Máquinas de corte | +| quality_tests | VT-004 | Tipos de prueba de calidad | +| quality_inspections | VT-004 | Inspecciones de calidad | +| quality_test_results | VT-004 | Resultados de pruebas | +| glass_lots | VT-005 | Lotes de vidrio | +| lot_consumption | VT-005 | Consumo de lotes | +| quotations | VT-006 | Cotizaciones | +| quotation_lines | VT-006 | Líneas de cotización | + +## ENUMs + +| Enum | Valores | +|------|---------| +| glass_type | clear, tinted, reflective, low_e, laminated, tempered | +| production_status | draft, scheduled, in_progress, cutting, tempering, quality_check, completed, cancelled | +| furnace_status | idle, preheating, heating, cooling, maintenance | +| inspection_result | pending, passed, failed, conditional | +| quotation_status | draft, sent, approved, rejected, expired | + +## Row Level Security + +Todas las tablas tienen RLS con: +```sql +tenant_id = current_setting('app.current_tenant_id', true)::UUID +``` + +## Funciones Específicas + +### vidrio.calculate_area_m2(width_mm, height_mm) +Calcula el área en metros cuadrados a partir de dimensiones en milímetros. + +```sql +SELECT vidrio.calculate_area_m2(1500, 2000); +-- Resultado: 3.00 m² +``` + +## Consideraciones Especiales + +- **Cálculos de área**: Siempre en m² para costeo +- **Trazabilidad**: Lotes vinculados a piezas producidas +- **Control de calidad**: Pruebas obligatorias antes de entrega +- **Capacidad hornos**: Máximo área por batch configurable + +## Referencias + +- [HERENCIA-ERP-CORE.md](../orchestration/00-guidelines/HERENCIA-ERP-CORE.md) +- [DATABASE_INVENTORY.yml](../orchestration/inventarios/DATABASE_INVENTORY.yml) diff --git a/init/00-extensions.sql b/init/00-extensions.sql new file mode 100644 index 0000000..907457e --- /dev/null +++ b/init/00-extensions.sql @@ -0,0 +1,23 @@ +-- ============================================================================ +-- EXTENSIONES PostgreSQL - ERP Vidrio Templado +-- ============================================================================ +-- 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 cálculos geométricos (áreas de vidrio) +-- Ya debería estar en ERP-Core, pero por si acaso +CREATE EXTENSION IF NOT EXISTS pg_trgm; + +-- ============================================================================ +-- FIN EXTENSIONES +-- ============================================================================ diff --git a/init/01-create-schemas.sql b/init/01-create-schemas.sql new file mode 100644 index 0000000..b70c781 --- /dev/null +++ b/init/01-create-schemas.sql @@ -0,0 +1,15 @@ +-- ============================================================================ +-- SCHEMAS - ERP Vidrio Templado +-- ============================================================================ +-- Versión: 1.0.0 +-- Fecha: 2025-12-09 +-- ============================================================================ + +-- Schema principal para operaciones de vidrio templado +CREATE SCHEMA IF NOT EXISTS vidrio; + +COMMENT ON SCHEMA vidrio IS 'Schema para operaciones de manufactura de vidrio templado'; + +-- ============================================================================ +-- FIN SCHEMAS +-- ============================================================================ diff --git a/init/02-rls-functions.sql b/init/02-rls-functions.sql new file mode 100644 index 0000000..fbe0a3a --- /dev/null +++ b/init/02-rls-functions.sql @@ -0,0 +1,30 @@ +-- ============================================================================ +-- FUNCIONES RLS - ERP Vidrio Templado +-- ============================================================================ +-- 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 para calcular área de vidrio en m2 +CREATE OR REPLACE FUNCTION vidrio.calculate_area_m2( + width_mm DECIMAL, + height_mm DECIMAL +) +RETURNS DECIMAL AS $$ +BEGIN + RETURN (width_mm / 1000.0) * (height_mm / 1000.0); +END; +$$ LANGUAGE plpgsql IMMUTABLE; + +COMMENT ON FUNCTION vidrio.calculate_area_m2 IS +'Calcula el área en metros cuadrados a partir de dimensiones en milímetros'; + +-- ============================================================================ +-- FIN FUNCIONES RLS +-- ============================================================================ diff --git a/init/03-vidrio-tables.sql b/init/03-vidrio-tables.sql new file mode 100644 index 0000000..d60c034 --- /dev/null +++ b/init/03-vidrio-tables.sql @@ -0,0 +1,716 @@ +-- ============================================================================ +-- TABLAS VIDRIO TEMPLADO - ERP Vidrio +-- ============================================================================ +-- Módulos: VT-001 (Producción), VT-002 (Calidad), VT-003 (Inventario), +-- VT-004 (Maquinaria), VT-005 (Trazabilidad), VT-006 (Cotizaciones) +-- Versión: 1.0.0 +-- Fecha: 2025-12-09 +-- ============================================================================ +-- PREREQUISITOS: +-- 1. ERP-Core instalado (auth, core, inventory, sales) +-- 2. Schema vidrio creado +-- ============================================================================ + +-- ============================================================================ +-- TYPES (ENUMs) +-- ============================================================================ + +DO $$ BEGIN + CREATE TYPE vidrio.glass_type AS ENUM ( + 'clear', 'tinted', 'reflective', 'low_e', 'laminated', 'tempered', 'insulated' + ); +EXCEPTION WHEN duplicate_object THEN NULL; END $$; + +DO $$ BEGIN + CREATE TYPE vidrio.production_status AS ENUM ( + 'draft', 'scheduled', 'cutting', 'processing', 'tempering', 'quality_check', 'completed', 'cancelled' + ); +EXCEPTION WHEN duplicate_object THEN NULL; END $$; + +DO $$ BEGIN + CREATE TYPE vidrio.quality_result AS ENUM ( + 'pending', 'passed', 'failed', 'conditional' + ); +EXCEPTION WHEN duplicate_object THEN NULL; END $$; + +DO $$ BEGIN + CREATE TYPE vidrio.furnace_status AS ENUM ( + 'idle', 'heating', 'ready', 'in_use', 'cooling', 'maintenance' + ); +EXCEPTION WHEN duplicate_object THEN NULL; END $$; + +DO $$ BEGIN + CREATE TYPE vidrio.edge_type AS ENUM ( + 'flat_polished', 'beveled', 'pencil', 'ogee', 'waterfall', 'raw' + ); +EXCEPTION WHEN duplicate_object THEN NULL; END $$; + +-- ============================================================================ +-- CATÁLOGOS BASE +-- ============================================================================ + +-- Tabla: glass_catalog (Catálogo de tipos de vidrio) +CREATE TABLE IF NOT EXISTS vidrio.glass_catalog ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE, + + -- Identificación + code VARCHAR(30) NOT NULL, + name VARCHAR(100) NOT NULL, + description TEXT, + + -- Tipo y características + glass_type vidrio.glass_type NOT NULL, + thickness_mm DECIMAL(5,2) NOT NULL, -- 3, 4, 5, 6, 8, 10, 12, 15, 19 mm + color VARCHAR(50), + + -- Propiedades físicas + weight_per_m2 DECIMAL(8,3), -- kg/m2 + max_width_mm INTEGER, + max_height_mm INTEGER, + + -- Precios base + price_per_m2 DECIMAL(12,2), + cost_per_m2 DECIMAL(12,2), + + -- Control + is_active BOOLEAN NOT NULL DEFAULT TRUE, + + -- 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_glass_catalog_code UNIQUE (tenant_id, code) +); + +-- Tabla: process_catalog (Catálogo de procesos) +CREATE TABLE IF NOT EXISTS vidrio.process_catalog ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE, + + code VARCHAR(30) NOT NULL, + name VARCHAR(100) NOT NULL, + description TEXT, + + -- Tipo de proceso + process_type VARCHAR(50) NOT NULL, -- cutting, edging, drilling, tempering, laminating + + -- Costos + cost_per_unit DECIMAL(12,2), + cost_per_m2 DECIMAL(12,2), + time_minutes INTEGER, + + 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_process_catalog_code UNIQUE (tenant_id, code) +); + +-- ============================================================================ +-- PRODUCCIÓN (VT-001) +-- ============================================================================ + +-- Tabla: production_orders (Órdenes de producción) +CREATE TABLE IF NOT EXISTS vidrio.production_orders ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE, + + -- Número de orden + order_number VARCHAR(30) NOT NULL, + + -- Referencias + sale_order_id UUID, -- FK a sales.sale_orders (ERP Core) + customer_id UUID, -- FK a core.partners (ERP Core) + + -- Estado + status vidrio.production_status NOT NULL DEFAULT 'draft', + + -- Fechas + order_date DATE NOT NULL DEFAULT CURRENT_DATE, + due_date DATE NOT NULL, + start_date TIMESTAMPTZ, + end_date TIMESTAMPTZ, + + -- Prioridad + priority INTEGER DEFAULT 5 CHECK (priority BETWEEN 1 AND 10), + + -- Totales + total_pieces INTEGER DEFAULT 0, + total_area_m2 DECIMAL(12,4) DEFAULT 0, + completed_pieces INTEGER DEFAULT 0, + + -- Notas + notes TEXT, + internal_notes 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), + deleted_at TIMESTAMPTZ, + deleted_by UUID REFERENCES auth.users(id), + + CONSTRAINT uq_production_orders_number UNIQUE (tenant_id, order_number) +); + +-- Tabla: production_lines (Piezas de producción) +CREATE TABLE IF NOT EXISTS vidrio.production_lines ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE, + production_order_id UUID NOT NULL REFERENCES vidrio.production_orders(id) ON DELETE CASCADE, + + -- Identificación de pieza + line_number INTEGER NOT NULL, + piece_code VARCHAR(50), + + -- Tipo de vidrio + glass_catalog_id UUID NOT NULL REFERENCES vidrio.glass_catalog(id), + + -- Dimensiones (en mm) + width_mm DECIMAL(8,2) NOT NULL, + height_mm DECIMAL(8,2) NOT NULL, + quantity INTEGER NOT NULL DEFAULT 1, + + -- Área calculada + area_m2 DECIMAL(12,6) GENERATED ALWAYS AS ( + (width_mm / 1000.0) * (height_mm / 1000.0) * quantity + ) STORED, + + -- Acabados + edge_type vidrio.edge_type DEFAULT 'flat_polished', + has_holes BOOLEAN DEFAULT FALSE, + hole_count INTEGER DEFAULT 0, + has_cutouts BOOLEAN DEFAULT FALSE, + + -- Estado + status vidrio.production_status DEFAULT 'draft', + + -- Lote de producción + lot_id UUID, + furnace_batch_id UUID, + + -- Notas + notes TEXT, + drawing_url VARCHAR(500), + + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + created_by UUID REFERENCES auth.users(id), + updated_at TIMESTAMPTZ, + updated_by UUID REFERENCES auth.users(id) +); + +-- ============================================================================ +-- MAQUINARIA (VT-004) +-- ============================================================================ + +-- Tabla: furnaces (Hornos de templado) +CREATE TABLE IF NOT EXISTS vidrio.furnaces ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE, + + code VARCHAR(30) NOT NULL, + name VARCHAR(100) NOT NULL, + + -- Capacidad + max_width_mm INTEGER NOT NULL, + max_height_mm INTEGER NOT NULL, + min_thickness_mm DECIMAL(4,2), + max_thickness_mm DECIMAL(4,2), + + -- Estado + status vidrio.furnace_status DEFAULT 'idle', + current_temperature INTEGER, + + -- Mantenimiento + last_maintenance_date DATE, + next_maintenance_date DATE, + + 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_furnaces_code UNIQUE (tenant_id, code) +); + +-- Tabla: furnace_batches (Lotes de hornada) +CREATE TABLE IF NOT EXISTS vidrio.furnace_batches ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE, + furnace_id UUID NOT NULL REFERENCES vidrio.furnaces(id), + + -- Identificación + batch_number VARCHAR(30) NOT NULL, + + -- Tiempos + start_time TIMESTAMPTZ NOT NULL, + end_time TIMESTAMPTZ, + + -- Parámetros + temperature INTEGER NOT NULL, -- Temperatura objetivo + cycle_time_minutes INTEGER, -- Tiempo de ciclo + + -- Conteo + pieces_count INTEGER DEFAULT 0, + pieces_passed INTEGER DEFAULT 0, + pieces_failed INTEGER DEFAULT 0, + + -- Operador + operator_id UUID REFERENCES auth.users(id), + + -- Notas + notes TEXT, + + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + created_by UUID REFERENCES auth.users(id), + + CONSTRAINT uq_furnace_batches_number UNIQUE (tenant_id, batch_number) +); + +-- Tabla: cutting_machines (Máquinas de corte) +CREATE TABLE IF NOT EXISTS vidrio.cutting_machines ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE, + + code VARCHAR(30) NOT NULL, + name VARCHAR(100) NOT NULL, + machine_type VARCHAR(50), -- manual, semi_auto, cnc + + -- Capacidad + max_width_mm INTEGER, + max_height_mm INTEGER, + + -- Estado + is_active BOOLEAN NOT NULL DEFAULT TRUE, + + -- Mantenimiento + last_maintenance_date DATE, + + 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_cutting_machines_code UNIQUE (tenant_id, code) +); + +-- ============================================================================ +-- CALIDAD (VT-002) +-- ============================================================================ + +-- Tabla: quality_tests (Pruebas de calidad) +CREATE TABLE IF NOT EXISTS vidrio.quality_tests ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE, + + code VARCHAR(30) NOT NULL, + name VARCHAR(100) NOT NULL, + description TEXT, + + -- Tipo de prueba + test_type VARCHAR(50) NOT NULL, -- visual, fragmentation, impact, heat_soak + + -- Parámetros + min_value DECIMAL(12,4), + max_value DECIMAL(12,4), + unit VARCHAR(20), + + is_mandatory 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), + + CONSTRAINT uq_quality_tests_code UNIQUE (tenant_id, code) +); + +-- Tabla: quality_inspections (Inspecciones de calidad) +CREATE TABLE IF NOT EXISTS vidrio.quality_inspections ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE, + + -- Referencia + production_line_id UUID REFERENCES vidrio.production_lines(id), + furnace_batch_id UUID REFERENCES vidrio.furnace_batches(id), + + -- Número + inspection_number VARCHAR(30) NOT NULL, + inspection_date TIMESTAMPTZ NOT NULL DEFAULT NOW(), + + -- Resultado general + result vidrio.quality_result NOT NULL DEFAULT 'pending', + + -- Inspector + inspector_id UUID NOT NULL REFERENCES auth.users(id), + + -- Notas + notes TEXT, + rejection_reason TEXT, + + 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_quality_inspections_number UNIQUE (tenant_id, inspection_number) +); + +-- Tabla: quality_test_results (Resultados de pruebas) +CREATE TABLE IF NOT EXISTS vidrio.quality_test_results ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE, + inspection_id UUID NOT NULL REFERENCES vidrio.quality_inspections(id) ON DELETE CASCADE, + test_id UUID NOT NULL REFERENCES vidrio.quality_tests(id), + + -- Resultado + result vidrio.quality_result NOT NULL, + measured_value DECIMAL(12,4), + + -- Notas + notes TEXT, + photo_url VARCHAR(500), + + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + created_by UUID REFERENCES auth.users(id) +); + +-- ============================================================================ +-- TRAZABILIDAD (VT-005) +-- ============================================================================ + +-- Tabla: glass_lots (Lotes de vidrio crudo) +CREATE TABLE IF NOT EXISTS vidrio.glass_lots ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE, + + -- Identificación + lot_number VARCHAR(30) NOT NULL, + glass_catalog_id UUID NOT NULL REFERENCES vidrio.glass_catalog(id), + + -- Proveedor + supplier_id UUID, -- FK a core.partners (ERP Core) + supplier_lot VARCHAR(50), + purchase_order_id UUID, -- FK a purchase.purchase_orders (ERP Core) + + -- Recepción + receipt_date DATE NOT NULL, + quantity_sheets INTEGER NOT NULL, + quantity_remaining INTEGER NOT NULL, + + -- Dimensiones del lote + sheet_width_mm INTEGER, + sheet_height_mm INTEGER, + + -- Calidad + quality_certificate VARCHAR(100), + inspection_status vidrio.quality_result DEFAULT 'pending', + + -- Fechas + expiry_date DATE, + + -- Notas + notes TEXT, + + 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_glass_lots_number UNIQUE (tenant_id, lot_number) +); + +-- Tabla: lot_consumption (Consumo de lotes) +CREATE TABLE IF NOT EXISTS vidrio.lot_consumption ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE, + glass_lot_id UUID NOT NULL REFERENCES vidrio.glass_lots(id), + production_line_id UUID NOT NULL REFERENCES vidrio.production_lines(id), + + quantity_sheets INTEGER NOT NULL DEFAULT 1, + consumption_date TIMESTAMPTZ NOT NULL DEFAULT NOW(), + + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + created_by UUID REFERENCES auth.users(id) +); + +-- ============================================================================ +-- COTIZACIONES (VT-006) +-- ============================================================================ + +-- Tabla: quotations (Cotizaciones de vidrio) +CREATE TABLE IF NOT EXISTS vidrio.quotations ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE, + + -- Número + quotation_number VARCHAR(30) NOT NULL, + + -- Cliente + customer_id UUID NOT NULL, -- FK a core.partners (ERP Core) + contact_name VARCHAR(200), + contact_email VARCHAR(255), + contact_phone VARCHAR(20), + + -- Estado + status VARCHAR(20) NOT NULL DEFAULT 'draft', -- draft, sent, confirmed, cancelled, expired + + -- Fechas + quotation_date DATE NOT NULL DEFAULT CURRENT_DATE, + valid_until DATE NOT NULL, + + -- Proyecto/Obra + project_name VARCHAR(200), + project_location VARCHAR(255), + + -- Totales + subtotal DECIMAL(14,2) DEFAULT 0, + discount_percent DECIMAL(5,2) DEFAULT 0, + discount_amount DECIMAL(14,2) DEFAULT 0, + tax_amount DECIMAL(14,2) DEFAULT 0, + total DECIMAL(14,2) DEFAULT 0, + + -- Conversión + sale_order_id UUID, -- FK a sales.sale_orders cuando se confirma + + -- Notas + notes TEXT, + terms_conditions TEXT, + + 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_quotations_number UNIQUE (tenant_id, quotation_number) +); + +-- Tabla: quotation_lines (Líneas de cotización) +CREATE TABLE IF NOT EXISTS vidrio.quotation_lines ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE, + quotation_id UUID NOT NULL REFERENCES vidrio.quotations(id) ON DELETE CASCADE, + + -- Tipo de vidrio + glass_catalog_id UUID NOT NULL REFERENCES vidrio.glass_catalog(id), + + -- Dimensiones + width_mm DECIMAL(8,2) NOT NULL, + height_mm DECIMAL(8,2) NOT NULL, + quantity INTEGER NOT NULL DEFAULT 1, + + -- Área + area_m2 DECIMAL(12,6) GENERATED ALWAYS AS ( + (width_mm / 1000.0) * (height_mm / 1000.0) * quantity + ) STORED, + + -- Acabados + edge_type vidrio.edge_type DEFAULT 'flat_polished', + has_holes BOOLEAN DEFAULT FALSE, + hole_count INTEGER DEFAULT 0, + has_cutouts BOOLEAN DEFAULT FALSE, + + -- Precios + price_per_m2 DECIMAL(12,2) NOT NULL, + processing_cost DECIMAL(12,2) DEFAULT 0, + subtotal DECIMAL(14,2) NOT NULL, + + -- Descripción + description TEXT, + + -- Orden + sequence INTEGER DEFAULT 1, + + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + created_by UUID REFERENCES auth.users(id) +); + +-- ============================================================================ +-- ÍNDICES +-- ============================================================================ + +-- Glass catalog +CREATE INDEX IF NOT EXISTS idx_glass_catalog_tenant ON vidrio.glass_catalog(tenant_id); +CREATE INDEX IF NOT EXISTS idx_glass_catalog_type ON vidrio.glass_catalog(glass_type); + +-- Process catalog +CREATE INDEX IF NOT EXISTS idx_process_catalog_tenant ON vidrio.process_catalog(tenant_id); + +-- Production orders +CREATE INDEX IF NOT EXISTS idx_production_orders_tenant ON vidrio.production_orders(tenant_id); +CREATE INDEX IF NOT EXISTS idx_production_orders_status ON vidrio.production_orders(status); +CREATE INDEX IF NOT EXISTS idx_production_orders_customer ON vidrio.production_orders(customer_id); +CREATE INDEX IF NOT EXISTS idx_production_orders_date ON vidrio.production_orders(order_date); +CREATE INDEX IF NOT EXISTS idx_production_orders_due ON vidrio.production_orders(due_date); + +-- Production lines +CREATE INDEX IF NOT EXISTS idx_production_lines_tenant ON vidrio.production_lines(tenant_id); +CREATE INDEX IF NOT EXISTS idx_production_lines_order ON vidrio.production_lines(production_order_id); +CREATE INDEX IF NOT EXISTS idx_production_lines_glass ON vidrio.production_lines(glass_catalog_id); +CREATE INDEX IF NOT EXISTS idx_production_lines_status ON vidrio.production_lines(status); + +-- Furnaces +CREATE INDEX IF NOT EXISTS idx_furnaces_tenant ON vidrio.furnaces(tenant_id); +CREATE INDEX IF NOT EXISTS idx_furnaces_status ON vidrio.furnaces(status); + +-- Furnace batches +CREATE INDEX IF NOT EXISTS idx_furnace_batches_tenant ON vidrio.furnace_batches(tenant_id); +CREATE INDEX IF NOT EXISTS idx_furnace_batches_furnace ON vidrio.furnace_batches(furnace_id); +CREATE INDEX IF NOT EXISTS idx_furnace_batches_date ON vidrio.furnace_batches(start_time); + +-- Quality inspections +CREATE INDEX IF NOT EXISTS idx_quality_inspections_tenant ON vidrio.quality_inspections(tenant_id); +CREATE INDEX IF NOT EXISTS idx_quality_inspections_result ON vidrio.quality_inspections(result); + +-- Glass lots +CREATE INDEX IF NOT EXISTS idx_glass_lots_tenant ON vidrio.glass_lots(tenant_id); +CREATE INDEX IF NOT EXISTS idx_glass_lots_glass ON vidrio.glass_lots(glass_catalog_id); +CREATE INDEX IF NOT EXISTS idx_glass_lots_supplier ON vidrio.glass_lots(supplier_id); + +-- Quotations +CREATE INDEX IF NOT EXISTS idx_quotations_tenant ON vidrio.quotations(tenant_id); +CREATE INDEX IF NOT EXISTS idx_quotations_customer ON vidrio.quotations(customer_id); +CREATE INDEX IF NOT EXISTS idx_quotations_status ON vidrio.quotations(status); +CREATE INDEX IF NOT EXISTS idx_quotations_date ON vidrio.quotations(quotation_date); + +-- Quotation lines +CREATE INDEX IF NOT EXISTS idx_quotation_lines_tenant ON vidrio.quotation_lines(tenant_id); +CREATE INDEX IF NOT EXISTS idx_quotation_lines_quotation ON vidrio.quotation_lines(quotation_id); + +-- ============================================================================ +-- ROW LEVEL SECURITY +-- ============================================================================ + +ALTER TABLE vidrio.glass_catalog ENABLE ROW LEVEL SECURITY; +ALTER TABLE vidrio.process_catalog ENABLE ROW LEVEL SECURITY; +ALTER TABLE vidrio.production_orders ENABLE ROW LEVEL SECURITY; +ALTER TABLE vidrio.production_lines ENABLE ROW LEVEL SECURITY; +ALTER TABLE vidrio.furnaces ENABLE ROW LEVEL SECURITY; +ALTER TABLE vidrio.furnace_batches ENABLE ROW LEVEL SECURITY; +ALTER TABLE vidrio.cutting_machines ENABLE ROW LEVEL SECURITY; +ALTER TABLE vidrio.quality_tests ENABLE ROW LEVEL SECURITY; +ALTER TABLE vidrio.quality_inspections ENABLE ROW LEVEL SECURITY; +ALTER TABLE vidrio.quality_test_results ENABLE ROW LEVEL SECURITY; +ALTER TABLE vidrio.glass_lots ENABLE ROW LEVEL SECURITY; +ALTER TABLE vidrio.lot_consumption ENABLE ROW LEVEL SECURITY; +ALTER TABLE vidrio.quotations ENABLE ROW LEVEL SECURITY; +ALTER TABLE vidrio.quotation_lines ENABLE ROW LEVEL SECURITY; + +-- Políticas de aislamiento por tenant +DO $$ BEGIN + DROP POLICY IF EXISTS tenant_isolation_glass_catalog ON vidrio.glass_catalog; + CREATE POLICY tenant_isolation_glass_catalog ON vidrio.glass_catalog + 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_process_catalog ON vidrio.process_catalog; + CREATE POLICY tenant_isolation_process_catalog ON vidrio.process_catalog + 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_production_orders ON vidrio.production_orders; + CREATE POLICY tenant_isolation_production_orders ON vidrio.production_orders + 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_production_lines ON vidrio.production_lines; + CREATE POLICY tenant_isolation_production_lines ON vidrio.production_lines + 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_furnaces ON vidrio.furnaces; + CREATE POLICY tenant_isolation_furnaces ON vidrio.furnaces + 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_furnace_batches ON vidrio.furnace_batches; + CREATE POLICY tenant_isolation_furnace_batches ON vidrio.furnace_batches + 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_cutting_machines ON vidrio.cutting_machines; + CREATE POLICY tenant_isolation_cutting_machines ON vidrio.cutting_machines + 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_quality_tests ON vidrio.quality_tests; + CREATE POLICY tenant_isolation_quality_tests ON vidrio.quality_tests + 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_quality_inspections ON vidrio.quality_inspections; + CREATE POLICY tenant_isolation_quality_inspections ON vidrio.quality_inspections + 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_quality_test_results ON vidrio.quality_test_results; + CREATE POLICY tenant_isolation_quality_test_results ON vidrio.quality_test_results + 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_glass_lots ON vidrio.glass_lots; + CREATE POLICY tenant_isolation_glass_lots ON vidrio.glass_lots + 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_lot_consumption ON vidrio.lot_consumption; + CREATE POLICY tenant_isolation_lot_consumption ON vidrio.lot_consumption + 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_quotations ON vidrio.quotations; + CREATE POLICY tenant_isolation_quotations ON vidrio.quotations + 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_quotation_lines ON vidrio.quotation_lines; + CREATE POLICY tenant_isolation_quotation_lines ON vidrio.quotation_lines + FOR ALL USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID); +EXCEPTION WHEN undefined_object THEN NULL; END $$; + +-- ============================================================================ +-- COMENTARIOS +-- ============================================================================ + +COMMENT ON TABLE vidrio.glass_catalog IS 'Catálogo de tipos de vidrio'; +COMMENT ON TABLE vidrio.process_catalog IS 'Catálogo de procesos de manufactura'; +COMMENT ON TABLE vidrio.production_orders IS 'Órdenes de producción'; +COMMENT ON TABLE vidrio.production_lines IS 'Piezas individuales de producción'; +COMMENT ON TABLE vidrio.furnaces IS 'Hornos de templado'; +COMMENT ON TABLE vidrio.furnace_batches IS 'Lotes de hornada'; +COMMENT ON TABLE vidrio.cutting_machines IS 'Máquinas de corte'; +COMMENT ON TABLE vidrio.quality_tests IS 'Catálogo de pruebas de calidad'; +COMMENT ON TABLE vidrio.quality_inspections IS 'Inspecciones de calidad'; +COMMENT ON TABLE vidrio.quality_test_results IS 'Resultados de pruebas'; +COMMENT ON TABLE vidrio.glass_lots IS 'Lotes de vidrio crudo (trazabilidad)'; +COMMENT ON TABLE vidrio.lot_consumption IS 'Consumo de lotes en producción'; +COMMENT ON TABLE vidrio.quotations IS 'Cotizaciones de vidrio'; +COMMENT ON TABLE vidrio.quotation_lines IS 'Piezas cotizadas'; + +-- ============================================================================ +-- FIN TABLAS VIDRIO +-- Total: 14 tablas, 5 ENUMs +-- ============================================================================ diff --git a/schemas/04-financial-ext-schema-ddl.sql b/schemas/04-financial-ext-schema-ddl.sql new file mode 100644 index 0000000..50e686e --- /dev/null +++ b/schemas/04-financial-ext-schema-ddl.sql @@ -0,0 +1,53 @@ +-- ============================================================================ +-- FINANCIAL EXTENSIONS - FASE 8 ERP-Core +-- ERP Vidrio Templado +-- ============================================================================ + +CREATE SCHEMA IF NOT EXISTS financial; + +DO $$ BEGIN + CREATE TYPE financial.payment_method_type AS ENUM ('inbound', 'outbound'); +EXCEPTION WHEN duplicate_object THEN NULL; +END $$; + +-- 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 vidrio + aplica_anticipo BOOLEAN DEFAULT false, + porcentaje_anticipo NUMERIC(5,2) DEFAULT 0, + active BOOLEAN DEFAULT true, + created_at TIMESTAMPTZ DEFAULT NOW(), + CONSTRAINT uq_payment_methods_code UNIQUE(tenant_id, code) +); + +-- 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) DEFAULT 'percent', + value NUMERIC(10,2) DEFAULT 0, + days INTEGER DEFAULT 0, + applies_to VARCHAR(50), -- 'anticipo', 'produccion', 'instalacion', 'finiquito' + created_at TIMESTAMPTZ DEFAULT NOW() +); + +-- Índices y RLS +CREATE INDEX IF NOT EXISTS idx_payment_methods_tenant ON financial.payment_methods(tenant_id); +CREATE INDEX IF NOT EXISTS idx_payment_term_lines_tenant ON financial.payment_term_lines(tenant_id); + +ALTER TABLE financial.payment_methods ENABLE ROW LEVEL SECURITY; +ALTER TABLE financial.payment_term_lines ENABLE ROW LEVEL SECURITY; + +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_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); diff --git a/schemas/05-hr-ext-fase8-schema-ddl.sql b/schemas/05-hr-ext-fase8-schema-ddl.sql new file mode 100644 index 0000000..1302f8e --- /dev/null +++ b/schemas/05-hr-ext-fase8-schema-ddl.sql @@ -0,0 +1,181 @@ +-- ============================================================================ +-- HR EXTENSIONS - FASE 8 ERP-Core +-- ERP Vidrio Templado +-- ============================================================================ + +CREATE SCHEMA IF NOT EXISTS hr; + +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.payslip_status AS ENUM ('draft', 'verify', 'done', 'cancel'); +EXCEPTION WHEN duplicate_object THEN NULL; +END $$; + +-- Ubicaciones (áreas de producción) +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, + -- Extensiones vidrio + tipo_area VARCHAR(50), -- 'corte', 'pulido', 'templado', 'almacen', 'instalacion' + capacidad_m2 NUMERIC(10,2), + tiene_horno BOOLEAN DEFAULT false, + temperatura_max INTEGER, + active BOOLEAN DEFAULT true, + created_at TIMESTAMPTZ DEFAULT NOW() +); + +-- 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() +); + +-- Habilidades +CREATE TABLE IF NOT EXISTS hr.skills ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + tenant_id UUID NOT NULL, + skill_type_id UUID REFERENCES hr.skill_types(id), + name VARCHAR(100) NOT NULL, + -- Extensiones vidrio + tipo_proceso VARCHAR(50), -- 'corte', 'biselado', 'templado', 'instalacion' + maquina_habilitado VARCHAR(100), + created_at TIMESTAMPTZ DEFAULT NOW() +); + +-- Niveles +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 REFERENCES hr.skill_types(id), + name VARCHAR(100) NOT NULL, + level INTEGER DEFAULT 1, + created_at TIMESTAMPTZ DEFAULT NOW() +); + +-- 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 REFERENCES hr.skills(id), + skill_level_id UUID REFERENCES hr.skill_levels(id), + created_at TIMESTAMPTZ DEFAULT NOW() +); + +-- 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', + total_amount NUMERIC(12,2) DEFAULT 0, + proyecto_id UUID, + obra_id UUID, + created_at TIMESTAMPTZ DEFAULT NOW() +); + +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 DEFAULT CURRENT_DATE, + total_amount NUMERIC(12,2) NOT NULL, + state hr.expense_status DEFAULT 'draft', + created_at TIMESTAMPTZ DEFAULT NOW() +); + +-- 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, + tipo_pago VARCHAR(50), + active BOOLEAN DEFAULT true, + created_at TIMESTAMPTZ DEFAULT NOW(), + CONSTRAINT uq_payslip_structures_code UNIQUE(tenant_id, code) +); + +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), + date_from DATE NOT NULL, + date_to DATE NOT NULL, + state hr.payslip_status DEFAULT 'draft', + gross NUMERIC(12,2) DEFAULT 0, + net NUMERIC(12,2) DEFAULT 0, + area_id UUID REFERENCES hr.work_locations(id), + metros_producidos NUMERIC(10,2), + created_at TIMESTAMPTZ DEFAULT NOW() +); + +CREATE TABLE IF NOT EXISTS hr.payslip_lines ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + tenant_id UUID NOT NULL, + payslip_id UUID REFERENCES hr.payslips(id) ON DELETE CASCADE, + name VARCHAR(100) NOT NULL, + code VARCHAR(20), + amount NUMERIC(12,2) DEFAULT 0, + created_at TIMESTAMPTZ DEFAULT NOW() +); + +-- Índices +CREATE INDEX IF NOT EXISTS idx_work_locations_tenant ON hr.work_locations(tenant_id); +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_expense_sheets_tenant ON hr.expense_sheets(tenant_id); +CREATE INDEX IF NOT EXISTS idx_payslips_tenant ON hr.payslips(tenant_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); diff --git a/seeds/fase8/01-vidrio-catalogos.sql b/seeds/fase8/01-vidrio-catalogos.sql new file mode 100644 index 0000000..dab1b13 --- /dev/null +++ b/seeds/fase8/01-vidrio-catalogos.sql @@ -0,0 +1,50 @@ +-- ============================================================================ +-- SEED DATA: Catálogos Vidrio Templado +-- FASE-8 ERP-Core - ERP Vidrio Templado +-- ============================================================================ + +-- Métodos de pago +INSERT INTO financial.payment_methods (tenant_id, name, code, payment_type, aplica_anticipo, porcentaje_anticipo) +SELECT current_setting('app.current_tenant_id', true)::UUID, name, code, payment_type::financial.payment_method_type, anticipo, porc +FROM (VALUES + ('Anticipo 50%', 'anticipo', 'inbound', true, 50), + ('Pago Producción', 'produccion', 'inbound', false, 0), + ('Finiquito Instalación', 'finiquito', 'inbound', false, 0), + ('Transferencia', 'transfer', 'inbound', false, 0), + ('Efectivo', 'efectivo', 'inbound', false, 0) +) AS t(name, code, payment_type, anticipo, porc) +WHERE current_setting('app.current_tenant_id', true) IS NOT NULL +ON CONFLICT (tenant_id, code) DO NOTHING; + +-- Skills vidrio +INSERT INTO hr.skill_types (tenant_id, name) +SELECT current_setting('app.current_tenant_id', true)::UUID, name +FROM (VALUES ('Proceso de Producción'), ('Instalación'), ('Maquinaria'), ('Calidad')) AS t(name) +WHERE current_setting('app.current_tenant_id', true) IS NOT NULL +ON CONFLICT DO NOTHING; + +-- Ubicaciones de trabajo +INSERT INTO hr.work_locations (tenant_id, name, tipo_area, capacidad_m2, tiene_horno, temperatura_max) +SELECT current_setting('app.current_tenant_id', true)::UUID, name, tipo, cap, horno, temp +FROM (VALUES + ('Área de Corte', 'corte', 200.0, false, NULL), + ('Área de Pulido y Biselado', 'pulido', 100.0, false, NULL), + ('Horno de Templado', 'templado', 150.0, true, 700), + ('Almacén Materia Prima', 'almacen', 300.0, false, NULL), + ('Almacén Producto Terminado', 'almacen', 200.0, false, NULL), + ('Cuadrilla Instalación 1', 'instalacion', NULL, false, NULL), + ('Cuadrilla Instalación 2', 'instalacion', NULL, false, NULL) +) AS t(name, tipo, cap, horno, temp) +WHERE current_setting('app.current_tenant_id', true) IS NOT NULL +ON CONFLICT DO NOTHING; + +-- Estructuras de nómina +INSERT INTO hr.payslip_structures (tenant_id, name, code, tipo_pago) +SELECT current_setting('app.current_tenant_id', true)::UUID, name, code, tipo +FROM (VALUES + ('Nómina Semanal', 'NOM-SEM', 'semanal'), + ('Destajo Producción', 'DEST-PROD', 'destajo'), + ('Destajo Instalación', 'DEST-INST', 'destajo') +) AS t(name, code, tipo) +WHERE current_setting('app.current_tenant_id', true) IS NOT NULL +ON CONFLICT (tenant_id, code) DO NOTHING;