Some checks failed
CI Pipeline / Lint & Type Check (push) Has been cancelled
CI Pipeline / Validate SSOT Constants (push) Has been cancelled
CI Pipeline / Backend Tests (push) Has been cancelled
CI Pipeline / Frontend Tests (push) Has been cancelled
CI Pipeline / Build (push) Has been cancelled
CI Pipeline / Docker Build (push) Has been cancelled
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1395 lines
52 KiB
Markdown
1395 lines
52 KiB
Markdown
# FASE 3: Plan de Implementación - ERP Construcción
|
|
|
|
**Proyecto:** erp-construccion
|
|
**Fecha:** 2026-01-04
|
|
**Estado:** Completado
|
|
**Base:** FASE-2-ANALISIS-DETALLADO.md
|
|
|
|
---
|
|
|
|
## 1. Archivos DDL a Crear
|
|
|
|
### 1.1 Archivo: 08-financial-ext-schema-ddl.sql
|
|
|
|
**Propósito:** Extensiones financieras para construcción
|
|
|
|
```sql
|
|
-- ============================================
|
|
-- ERP CONSTRUCCIÓN - EXTENSIONES FINANCIAL
|
|
-- Basado en: ERP-Core FASE-8 (COR-035 a COR-039)
|
|
-- Fecha: 2026-01-04
|
|
-- ============================================
|
|
|
|
-- Verificar schema
|
|
CREATE SCHEMA IF NOT EXISTS financial;
|
|
|
|
-- ============================================
|
|
-- 1. PAYMENT TERM LINES (COR-035)
|
|
-- Términos de pago para estimaciones de obra
|
|
-- ============================================
|
|
|
|
CREATE TABLE IF NOT EXISTS financial.payment_term_lines (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id),
|
|
payment_term_id UUID NOT NULL REFERENCES financial.payment_terms(id) ON DELETE CASCADE,
|
|
sequence INTEGER NOT NULL DEFAULT 10,
|
|
value VARCHAR(20) NOT NULL DEFAULT 'percent',
|
|
value_amount DECIMAL(10,4) NOT NULL DEFAULT 100,
|
|
days INTEGER NOT NULL DEFAULT 0,
|
|
day_of_month INTEGER,
|
|
end_month BOOLEAN DEFAULT FALSE,
|
|
-- Extensión construcción
|
|
applies_to VARCHAR(50), -- 'anticipo', 'estimacion', 'retencion', 'finiquito'
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
|
|
CONSTRAINT chk_value CHECK (value IN ('percent', 'fixed', 'balance')),
|
|
CONSTRAINT chk_applies_to CHECK (applies_to IS NULL OR applies_to IN ('anticipo', 'estimacion', 'retencion', 'finiquito'))
|
|
);
|
|
|
|
-- Í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_term ON financial.payment_term_lines(payment_term_id);
|
|
|
|
-- RLS
|
|
ALTER TABLE financial.payment_term_lines ENABLE ROW LEVEL SECURITY;
|
|
|
|
CREATE POLICY tenant_isolation_payment_term_lines ON financial.payment_term_lines
|
|
FOR ALL USING (tenant_id = current_setting('app.current_tenant_id')::UUID);
|
|
|
|
-- ============================================
|
|
-- 2. INCOTERMS (COR-036)
|
|
-- Para importación de maquinaria/materiales
|
|
-- ============================================
|
|
|
|
CREATE TABLE IF NOT EXISTS financial.incoterms (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
code VARCHAR(3) NOT NULL UNIQUE,
|
|
name VARCHAR(100) NOT NULL,
|
|
is_active BOOLEAN DEFAULT TRUE,
|
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
-- ============================================
|
|
-- 3. PAYMENT METHODS (COR-037)
|
|
-- Métodos de pago específicos de obra
|
|
-- ============================================
|
|
|
|
CREATE TYPE financial.payment_method_type AS ENUM ('inbound', 'outbound');
|
|
|
|
CREATE TABLE IF NOT EXISTS financial.payment_methods (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id),
|
|
name VARCHAR(100) NOT NULL,
|
|
code VARCHAR(50) NOT NULL,
|
|
payment_type financial.payment_method_type NOT NULL,
|
|
is_active BOOLEAN DEFAULT TRUE,
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
|
|
UNIQUE(tenant_id, code)
|
|
);
|
|
|
|
-- Índices
|
|
CREATE INDEX IF NOT EXISTS idx_payment_methods_tenant ON financial.payment_methods(tenant_id);
|
|
|
|
-- RLS
|
|
ALTER TABLE financial.payment_methods ENABLE ROW LEVEL SECURITY;
|
|
|
|
CREATE POLICY tenant_isolation_payment_methods ON financial.payment_methods
|
|
FOR ALL USING (tenant_id = current_setting('app.current_tenant_id')::UUID);
|
|
|
|
-- ============================================
|
|
-- 4. RECONCILE MODELS (COR-038)
|
|
-- Para conciliación de estimaciones
|
|
-- ============================================
|
|
|
|
CREATE TYPE financial.reconcile_model_type AS ENUM (
|
|
'writeoff_button',
|
|
'writeoff_suggestion',
|
|
'invoice_matching'
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS financial.reconcile_models (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id),
|
|
company_id UUID REFERENCES core.companies(id),
|
|
name VARCHAR(100) NOT NULL,
|
|
sequence INTEGER DEFAULT 10,
|
|
rule_type financial.reconcile_model_type NOT NULL DEFAULT 'writeoff_suggestion',
|
|
auto_reconcile BOOLEAN DEFAULT FALSE,
|
|
match_amount VARCHAR(20) DEFAULT 'percentage',
|
|
match_amount_min DECIMAL(5,2),
|
|
match_amount_max DECIMAL(5,2),
|
|
match_label VARCHAR(100),
|
|
is_active BOOLEAN DEFAULT TRUE,
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
|
|
CONSTRAINT chk_match_amount CHECK (match_amount IN ('percentage', 'fixed', 'any'))
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS financial.reconcile_model_lines (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
model_id UUID NOT NULL REFERENCES financial.reconcile_models(id) ON DELETE CASCADE,
|
|
sequence INTEGER DEFAULT 10,
|
|
account_id UUID NOT NULL REFERENCES financial.accounts(id),
|
|
amount_type VARCHAR(20) NOT NULL DEFAULT 'percentage',
|
|
amount_value DECIMAL(18,4) NOT NULL DEFAULT 100,
|
|
label VARCHAR(100),
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
|
|
CONSTRAINT chk_amount_type CHECK (amount_type IN ('percentage', 'fixed', 'regex'))
|
|
);
|
|
|
|
-- Índices
|
|
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.reconcile_models ENABLE ROW LEVEL SECURITY;
|
|
|
|
CREATE POLICY tenant_isolation_reconcile_models ON financial.reconcile_models
|
|
FOR ALL USING (tenant_id = current_setting('app.current_tenant_id')::UUID);
|
|
|
|
-- ============================================
|
|
-- 5. CAMPOS ADICIONALES (COR-039)
|
|
-- Modificar tablas existentes
|
|
-- ============================================
|
|
|
|
-- Campos en journal_entries (si existe)
|
|
DO $$
|
|
BEGIN
|
|
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema = 'financial' AND table_name = 'journal_entries') THEN
|
|
-- payment_state
|
|
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'financial' AND table_name = 'journal_entries' AND column_name = 'payment_state') THEN
|
|
ALTER TABLE financial.journal_entries ADD COLUMN payment_state VARCHAR(20) DEFAULT 'not_paid';
|
|
END IF;
|
|
-- amount_residual
|
|
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'financial' AND table_name = 'journal_entries' AND column_name = 'amount_residual') THEN
|
|
ALTER TABLE financial.journal_entries ADD COLUMN amount_residual DECIMAL(18,4) DEFAULT 0;
|
|
END IF;
|
|
-- invoice_date_due
|
|
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'financial' AND table_name = 'journal_entries' AND column_name = 'invoice_date_due') THEN
|
|
ALTER TABLE financial.journal_entries ADD COLUMN invoice_date_due DATE;
|
|
END IF;
|
|
-- incoterm_id
|
|
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'financial' AND table_name = 'journal_entries' AND column_name = 'incoterm_id') THEN
|
|
ALTER TABLE financial.journal_entries ADD COLUMN incoterm_id UUID REFERENCES financial.incoterms(id);
|
|
END IF;
|
|
END IF;
|
|
END $$;
|
|
|
|
-- Campos en payments (si existe)
|
|
DO $$
|
|
BEGIN
|
|
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema = 'financial' AND table_name = 'payments') THEN
|
|
-- is_matched
|
|
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'financial' AND table_name = 'payments' AND column_name = 'is_matched') THEN
|
|
ALTER TABLE financial.payments ADD COLUMN is_matched BOOLEAN DEFAULT FALSE;
|
|
END IF;
|
|
-- partner_bank_id
|
|
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'financial' AND table_name = 'payments' AND column_name = 'partner_bank_id') THEN
|
|
ALTER TABLE financial.payments ADD COLUMN partner_bank_id UUID;
|
|
END IF;
|
|
END IF;
|
|
END $$;
|
|
|
|
COMMENT ON TABLE financial.payment_term_lines IS 'ERP-Core FASE-8: Líneas de términos de pago para estimaciones';
|
|
COMMENT ON TABLE financial.incoterms IS 'ERP-Core FASE-8: Términos de comercio internacional';
|
|
COMMENT ON TABLE financial.payment_methods IS 'ERP-Core FASE-8: Métodos de pago de obra';
|
|
COMMENT ON TABLE financial.reconcile_models IS 'ERP-Core FASE-8: Modelos de conciliación';
|
|
COMMENT ON TABLE financial.reconcile_model_lines IS 'ERP-Core FASE-8: Líneas de modelos de conciliación';
|
|
```
|
|
|
|
**Líneas estimadas:** ~180
|
|
|
|
---
|
|
|
|
### 1.2 Archivo: 09-projects-ext-schema-ddl.sql
|
|
|
|
**Propósito:** Extensiones de proyectos adaptadas a construcción
|
|
|
|
```sql
|
|
-- ============================================
|
|
-- ERP CONSTRUCCIÓN - EXTENSIONES PROJECTS
|
|
-- Basado en: ERP-Core FASE-8 (COR-056 a COR-060)
|
|
-- Fecha: 2026-01-04
|
|
-- ============================================
|
|
|
|
-- ============================================
|
|
-- 1. COLLABORATORS (COR-056)
|
|
-- Colaboradores de obra: supervisores, peritos
|
|
-- ============================================
|
|
|
|
CREATE TABLE IF NOT EXISTS projects.collaborators (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id),
|
|
project_id UUID NOT NULL REFERENCES projects.projects(id) ON DELETE CASCADE,
|
|
partner_id UUID REFERENCES core.partners(id),
|
|
user_id UUID REFERENCES auth.users(id),
|
|
can_read BOOLEAN DEFAULT TRUE,
|
|
can_write BOOLEAN DEFAULT FALSE,
|
|
-- Extensión construcción
|
|
rol VARCHAR(50), -- 'supervisor', 'perito', 'representante', 'infonavit'
|
|
vigencia_desde DATE,
|
|
vigencia_hasta DATE,
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
invited_by UUID REFERENCES auth.users(id),
|
|
|
|
CONSTRAINT chk_partner_or_user CHECK (partner_id IS NOT NULL OR user_id IS NOT NULL),
|
|
CONSTRAINT chk_rol CHECK (rol IS NULL OR rol IN ('supervisor', 'perito', 'representante', 'infonavit', 'contratista', 'auditor'))
|
|
);
|
|
|
|
-- Índices
|
|
CREATE INDEX IF NOT EXISTS idx_collaborators_project ON projects.collaborators(project_id);
|
|
CREATE INDEX IF NOT EXISTS idx_collaborators_partner ON projects.collaborators(partner_id);
|
|
CREATE INDEX IF NOT EXISTS idx_collaborators_user ON projects.collaborators(user_id);
|
|
|
|
-- RLS
|
|
ALTER TABLE projects.collaborators ENABLE ROW LEVEL SECURITY;
|
|
|
|
CREATE POLICY tenant_isolation_collaborators ON projects.collaborators
|
|
FOR ALL USING (tenant_id = current_setting('app.current_tenant_id')::UUID);
|
|
|
|
-- ============================================
|
|
-- 2. RATINGS (COR-059)
|
|
-- Calificaciones de proveedores/contratistas
|
|
-- ============================================
|
|
|
|
CREATE TABLE IF NOT EXISTS projects.ratings (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id),
|
|
res_model VARCHAR(100) NOT NULL, -- 'construction.contratistas', 'core.partners'
|
|
res_id UUID NOT NULL,
|
|
rating DECIMAL(3,2) NOT NULL CHECK (rating >= 1 AND rating <= 5),
|
|
feedback TEXT,
|
|
partner_id UUID REFERENCES core.partners(id),
|
|
rated_by UUID REFERENCES auth.users(id),
|
|
-- Extensión construcción
|
|
proyecto_id UUID, -- Calificación por proyecto
|
|
tipo_trabajo VARCHAR(50), -- 'albanileria', 'electricidad', etc.
|
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
-- Índices
|
|
CREATE INDEX IF NOT EXISTS idx_ratings_tenant ON projects.ratings(tenant_id);
|
|
CREATE INDEX IF NOT EXISTS idx_ratings_res ON projects.ratings(res_model, res_id);
|
|
CREATE INDEX IF NOT EXISTS idx_ratings_proyecto ON projects.ratings(proyecto_id);
|
|
|
|
-- RLS
|
|
ALTER TABLE projects.ratings ENABLE ROW LEVEL SECURITY;
|
|
|
|
CREATE POLICY tenant_isolation_ratings ON projects.ratings
|
|
FOR ALL USING (tenant_id = current_setting('app.current_tenant_id')::UUID);
|
|
|
|
-- ============================================
|
|
-- 3. AVANCE PROGRAMADO (COR-060 adaptado)
|
|
-- Burndown chart adaptado a construcción
|
|
-- ============================================
|
|
|
|
CREATE TABLE IF NOT EXISTS construction.avance_programado (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id),
|
|
proyecto_id UUID NOT NULL REFERENCES construction.proyectos(id) ON DELETE CASCADE,
|
|
fecha DATE NOT NULL,
|
|
|
|
-- Avance físico
|
|
total_partidas INTEGER NOT NULL DEFAULT 0,
|
|
partidas_completadas INTEGER NOT NULL DEFAULT 0,
|
|
partidas_pendientes INTEGER NOT NULL DEFAULT 0,
|
|
partidas_en_proceso INTEGER NOT NULL DEFAULT 0,
|
|
|
|
-- Avance financiero
|
|
presupuesto_total DECIMAL(20,2) NOT NULL DEFAULT 0,
|
|
ejercido DECIMAL(20,2) NOT NULL DEFAULT 0,
|
|
por_ejercer DECIMAL(20,2) NOT NULL DEFAULT 0,
|
|
estimado_actual DECIMAL(20,2) NOT NULL DEFAULT 0,
|
|
|
|
-- Porcentajes
|
|
avance_fisico_programado DECIMAL(5,2) NOT NULL DEFAULT 0,
|
|
avance_fisico_real DECIMAL(5,2) NOT NULL DEFAULT 0,
|
|
avance_financiero_pct DECIMAL(5,2) NOT NULL DEFAULT 0,
|
|
|
|
-- Horas (para mano de obra)
|
|
horas_programadas DECIMAL(10,2) DEFAULT 0,
|
|
horas_ejecutadas DECIMAL(10,2) DEFAULT 0,
|
|
|
|
-- Metadatos
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
created_by UUID REFERENCES auth.users(id),
|
|
|
|
UNIQUE(proyecto_id, fecha)
|
|
);
|
|
|
|
-- Índices
|
|
CREATE INDEX IF NOT EXISTS idx_avance_proyecto ON construction.avance_programado(proyecto_id);
|
|
CREATE INDEX IF NOT EXISTS idx_avance_fecha ON construction.avance_programado(fecha);
|
|
|
|
-- RLS
|
|
ALTER TABLE construction.avance_programado ENABLE ROW LEVEL SECURITY;
|
|
|
|
CREATE POLICY tenant_isolation_avance ON construction.avance_programado
|
|
FOR ALL USING (tenant_id = current_setting('app.current_tenant_id')::UUID);
|
|
|
|
-- ============================================
|
|
-- 4. CAMPOS ADICIONALES EN PROYECTOS (COR-057)
|
|
-- ============================================
|
|
|
|
DO $$
|
|
BEGIN
|
|
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema = 'construction' AND table_name = 'proyectos') THEN
|
|
-- sequence
|
|
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'construction' AND table_name = 'proyectos' AND column_name = 'sequence') THEN
|
|
ALTER TABLE construction.proyectos ADD COLUMN sequence INTEGER DEFAULT 10;
|
|
END IF;
|
|
-- is_favorite
|
|
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'construction' AND table_name = 'proyectos' AND column_name = 'is_favorite') THEN
|
|
ALTER TABLE construction.proyectos ADD COLUMN is_favorite BOOLEAN DEFAULT FALSE;
|
|
END IF;
|
|
-- task_count (partidas)
|
|
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'construction' AND table_name = 'proyectos' AND column_name = 'partida_count') THEN
|
|
ALTER TABLE construction.proyectos ADD COLUMN partida_count INTEGER DEFAULT 0;
|
|
END IF;
|
|
-- open_task_count
|
|
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'construction' AND table_name = 'proyectos' AND column_name = 'partida_pendiente_count') THEN
|
|
ALTER TABLE construction.proyectos ADD COLUMN partida_pendiente_count INTEGER DEFAULT 0;
|
|
END IF;
|
|
-- closed_task_count
|
|
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'construction' AND table_name = 'proyectos' AND column_name = 'partida_completada_count') THEN
|
|
ALTER TABLE construction.proyectos ADD COLUMN partida_completada_count INTEGER DEFAULT 0;
|
|
END IF;
|
|
-- last_update_status
|
|
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'construction' AND table_name = 'proyectos' AND column_name = 'status_avance') THEN
|
|
ALTER TABLE construction.proyectos ADD COLUMN status_avance VARCHAR(20) DEFAULT 'on_track';
|
|
END IF;
|
|
END IF;
|
|
END $$;
|
|
|
|
-- ============================================
|
|
-- 5. TRIGGER PARA CONTEO DE PARTIDAS (COR-058)
|
|
-- ============================================
|
|
|
|
CREATE OR REPLACE FUNCTION construction.update_proyecto_partida_count()
|
|
RETURNS TRIGGER AS $$
|
|
DECLARE
|
|
v_proyecto_id UUID;
|
|
v_total INTEGER;
|
|
v_completadas INTEGER;
|
|
v_pendientes INTEGER;
|
|
BEGIN
|
|
-- Determinar proyecto_id según operación
|
|
IF TG_OP = 'DELETE' THEN
|
|
v_proyecto_id := OLD.proyecto_id;
|
|
ELSE
|
|
v_proyecto_id := NEW.proyecto_id;
|
|
END IF;
|
|
|
|
-- Calcular conteos
|
|
SELECT
|
|
COUNT(*),
|
|
COUNT(*) FILTER (WHERE estado = 'completada'),
|
|
COUNT(*) FILTER (WHERE estado IN ('pendiente', 'en_proceso'))
|
|
INTO v_total, v_completadas, v_pendientes
|
|
FROM construction.partidas
|
|
WHERE proyecto_id = v_proyecto_id;
|
|
|
|
-- Actualizar proyecto
|
|
UPDATE construction.proyectos
|
|
SET
|
|
partida_count = v_total,
|
|
partida_completada_count = v_completadas,
|
|
partida_pendiente_count = v_pendientes,
|
|
updated_at = NOW()
|
|
WHERE id = v_proyecto_id;
|
|
|
|
RETURN COALESCE(NEW, OLD);
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
-- Crear trigger si tabla partidas existe
|
|
DO $$
|
|
BEGIN
|
|
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema = 'construction' AND table_name = 'partidas') THEN
|
|
DROP TRIGGER IF EXISTS trg_update_proyecto_partida_count ON construction.partidas;
|
|
CREATE TRIGGER trg_update_proyecto_partida_count
|
|
AFTER INSERT OR UPDATE OR DELETE ON construction.partidas
|
|
FOR EACH ROW EXECUTE FUNCTION construction.update_proyecto_partida_count();
|
|
END IF;
|
|
END $$;
|
|
|
|
-- ============================================
|
|
-- 6. FUNCIÓN PARA SNAPSHOT DE AVANCE (COR-060)
|
|
-- ============================================
|
|
|
|
CREATE OR REPLACE FUNCTION construction.generate_avance_snapshot(
|
|
p_proyecto_id UUID,
|
|
p_fecha DATE DEFAULT CURRENT_DATE
|
|
)
|
|
RETURNS UUID AS $$
|
|
DECLARE
|
|
v_id UUID;
|
|
v_tenant_id UUID;
|
|
v_total_partidas INTEGER;
|
|
v_completadas INTEGER;
|
|
v_pendientes INTEGER;
|
|
v_en_proceso INTEGER;
|
|
v_presupuesto DECIMAL(20,2);
|
|
v_ejercido DECIMAL(20,2);
|
|
v_avance_fisico DECIMAL(5,2);
|
|
BEGIN
|
|
-- Obtener tenant
|
|
SELECT tenant_id INTO v_tenant_id
|
|
FROM construction.proyectos WHERE id = p_proyecto_id;
|
|
|
|
-- Contar partidas
|
|
SELECT
|
|
COUNT(*),
|
|
COUNT(*) FILTER (WHERE estado = 'completada'),
|
|
COUNT(*) FILTER (WHERE estado = 'pendiente'),
|
|
COUNT(*) FILTER (WHERE estado = 'en_proceso')
|
|
INTO v_total_partidas, v_completadas, v_pendientes, v_en_proceso
|
|
FROM construction.partidas
|
|
WHERE proyecto_id = p_proyecto_id;
|
|
|
|
-- Obtener montos del presupuesto
|
|
SELECT
|
|
COALESCE(SUM(importe_total), 0),
|
|
COALESCE(SUM(importe_ejercido), 0)
|
|
INTO v_presupuesto, v_ejercido
|
|
FROM construction.presupuesto_partidas
|
|
WHERE presupuesto_id IN (
|
|
SELECT id FROM construction.presupuestos WHERE proyecto_id = p_proyecto_id
|
|
);
|
|
|
|
-- Calcular avance físico
|
|
IF v_total_partidas > 0 THEN
|
|
v_avance_fisico := (v_completadas::DECIMAL / v_total_partidas) * 100;
|
|
ELSE
|
|
v_avance_fisico := 0;
|
|
END IF;
|
|
|
|
-- Insertar o actualizar snapshot
|
|
INSERT INTO construction.avance_programado (
|
|
tenant_id, proyecto_id, fecha,
|
|
total_partidas, partidas_completadas, partidas_pendientes, partidas_en_proceso,
|
|
presupuesto_total, ejercido, por_ejercer,
|
|
avance_fisico_real, avance_financiero_pct
|
|
) VALUES (
|
|
v_tenant_id, p_proyecto_id, p_fecha,
|
|
v_total_partidas, v_completadas, v_pendientes, v_en_proceso,
|
|
v_presupuesto, v_ejercido, v_presupuesto - v_ejercido,
|
|
v_avance_fisico,
|
|
CASE WHEN v_presupuesto > 0 THEN (v_ejercido / v_presupuesto) * 100 ELSE 0 END
|
|
)
|
|
ON CONFLICT (proyecto_id, fecha) DO UPDATE SET
|
|
total_partidas = EXCLUDED.total_partidas,
|
|
partidas_completadas = EXCLUDED.partidas_completadas,
|
|
partidas_pendientes = EXCLUDED.partidas_pendientes,
|
|
partidas_en_proceso = EXCLUDED.partidas_en_proceso,
|
|
presupuesto_total = EXCLUDED.presupuesto_total,
|
|
ejercido = EXCLUDED.ejercido,
|
|
por_ejercer = EXCLUDED.por_ejercer,
|
|
avance_fisico_real = EXCLUDED.avance_fisico_real,
|
|
avance_financiero_pct = EXCLUDED.avance_financiero_pct
|
|
RETURNING id INTO v_id;
|
|
|
|
RETURN v_id;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
COMMENT ON TABLE projects.collaborators IS 'ERP-Core FASE-8: Colaboradores de proyecto/obra';
|
|
COMMENT ON TABLE projects.ratings IS 'ERP-Core FASE-8: Calificaciones de proveedores/contratistas';
|
|
COMMENT ON TABLE construction.avance_programado IS 'ERP-Core FASE-8: Snapshots de avance de obra (burndown)';
|
|
COMMENT ON FUNCTION construction.generate_avance_snapshot IS 'Genera snapshot diario de avance de obra';
|
|
```
|
|
|
|
**Líneas estimadas:** ~280
|
|
|
|
---
|
|
|
|
## 2. Archivos DDL a Modificar
|
|
|
|
### 2.1 Modificar: 02-hr-schema-ddl.sql
|
|
|
|
**Agregar al final del archivo:**
|
|
|
|
```sql
|
|
-- ============================================
|
|
-- EXTENSIONES HR - ERP-Core FASE-8
|
|
-- COR-061 a COR-066
|
|
-- ============================================
|
|
|
|
-- ============================================
|
|
-- 1. WORK LOCATIONS (COR-062)
|
|
-- ============================================
|
|
|
|
CREATE TABLE IF NOT EXISTS hr.work_locations (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id),
|
|
company_id UUID REFERENCES core.companies(id),
|
|
name VARCHAR(100) NOT NULL,
|
|
location_type VARCHAR(50) DEFAULT 'office',
|
|
address_id UUID REFERENCES core.addresses(id),
|
|
is_active BOOLEAN DEFAULT TRUE,
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
|
|
CONSTRAINT chk_location_type CHECK (location_type IN ('office', 'home', 'other'))
|
|
);
|
|
|
|
-- RLS
|
|
ALTER TABLE hr.work_locations ENABLE ROW LEVEL SECURITY;
|
|
CREATE POLICY tenant_isolation_work_locations ON hr.work_locations
|
|
FOR ALL USING (tenant_id = current_setting('app.current_tenant_id')::UUID);
|
|
|
|
-- Extensión: Ubicaciones de obra
|
|
CREATE TABLE IF NOT EXISTS construction.ubicaciones_obra (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id),
|
|
work_location_id UUID REFERENCES hr.work_locations(id),
|
|
proyecto_id UUID NOT NULL REFERENCES construction.proyectos(id),
|
|
tipo VARCHAR(50) NOT NULL,
|
|
nombre VARCHAR(100),
|
|
coordenadas_lat DECIMAL(10,8),
|
|
coordenadas_lng DECIMAL(11,8),
|
|
radio_metros INTEGER DEFAULT 100,
|
|
is_active BOOLEAN DEFAULT TRUE,
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
|
|
CONSTRAINT chk_tipo CHECK (tipo IN ('frente', 'almacen', 'oficina_obra', 'caseta', 'acceso'))
|
|
);
|
|
|
|
ALTER TABLE construction.ubicaciones_obra ENABLE ROW LEVEL SECURITY;
|
|
CREATE POLICY tenant_isolation_ubicaciones_obra ON construction.ubicaciones_obra
|
|
FOR ALL USING (tenant_id = current_setting('app.current_tenant_id')::UUID);
|
|
|
|
-- ============================================
|
|
-- 2. SKILLS SYSTEM (COR-063)
|
|
-- ============================================
|
|
|
|
CREATE TABLE IF NOT EXISTS hr.skill_types (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id),
|
|
name VARCHAR(100) NOT NULL,
|
|
is_active BOOLEAN DEFAULT TRUE,
|
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS hr.skills (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id),
|
|
skill_type_id UUID NOT NULL REFERENCES hr.skill_types(id) ON DELETE CASCADE,
|
|
name VARCHAR(100) NOT NULL,
|
|
is_active BOOLEAN DEFAULT TRUE,
|
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS hr.skill_levels (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id),
|
|
skill_type_id UUID NOT NULL REFERENCES hr.skill_types(id) ON DELETE CASCADE,
|
|
name VARCHAR(50) NOT NULL,
|
|
level INTEGER NOT NULL DEFAULT 1,
|
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS hr.employee_skills (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
employee_id UUID NOT NULL REFERENCES hr.employees(id) ON DELETE CASCADE,
|
|
skill_id UUID NOT NULL REFERENCES hr.skills(id),
|
|
skill_level_id UUID REFERENCES hr.skill_levels(id),
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
|
|
UNIQUE(employee_id, skill_id)
|
|
);
|
|
|
|
-- RLS para skills
|
|
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;
|
|
|
|
CREATE POLICY tenant_isolation_skill_types ON hr.skill_types
|
|
FOR ALL USING (tenant_id = current_setting('app.current_tenant_id')::UUID);
|
|
CREATE POLICY tenant_isolation_skills ON hr.skills
|
|
FOR ALL USING (tenant_id = current_setting('app.current_tenant_id')::UUID);
|
|
CREATE POLICY tenant_isolation_skill_levels ON hr.skill_levels
|
|
FOR ALL USING (tenant_id = current_setting('app.current_tenant_id')::UUID);
|
|
|
|
-- ============================================
|
|
-- 3. EXPENSE SYSTEM (COR-064)
|
|
-- ============================================
|
|
|
|
CREATE TYPE hr.expense_status AS ENUM ('draft', 'submitted', 'approved', 'posted', 'paid', 'rejected');
|
|
|
|
CREATE TABLE IF NOT EXISTS hr.expense_sheets (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id),
|
|
company_id UUID REFERENCES core.companies(id),
|
|
employee_id UUID NOT NULL REFERENCES hr.employees(id),
|
|
name VARCHAR(100) NOT NULL,
|
|
status hr.expense_status DEFAULT 'draft',
|
|
total_amount DECIMAL(18,4) DEFAULT 0,
|
|
currency_id UUID REFERENCES core.currencies(id),
|
|
approved_by UUID REFERENCES auth.users(id),
|
|
approved_at TIMESTAMPTZ,
|
|
-- Extensión construcción
|
|
proyecto_id UUID, -- Asignar a proyecto
|
|
centro_costo VARCHAR(50), -- Por frente/etapa
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS hr.expenses (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id),
|
|
expense_sheet_id UUID REFERENCES hr.expense_sheets(id) ON DELETE SET NULL,
|
|
employee_id UUID NOT NULL REFERENCES hr.employees(id),
|
|
name VARCHAR(200) NOT NULL,
|
|
product_id UUID REFERENCES inventory.products(id),
|
|
unit_amount DECIMAL(18,4) NOT NULL,
|
|
quantity DECIMAL(10,4) DEFAULT 1,
|
|
total_amount DECIMAL(18,4) GENERATED ALWAYS AS (unit_amount * quantity) STORED,
|
|
currency_id UUID REFERENCES core.currencies(id),
|
|
date DATE NOT NULL DEFAULT CURRENT_DATE,
|
|
reference VARCHAR(100),
|
|
description TEXT,
|
|
-- Extensión construcción
|
|
proyecto_id UUID,
|
|
partida_id UUID, -- Asignar a partida específica
|
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
ALTER TABLE hr.expense_sheets ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE hr.expenses ENABLE ROW LEVEL SECURITY;
|
|
|
|
CREATE POLICY tenant_isolation_expense_sheets ON hr.expense_sheets
|
|
FOR ALL USING (tenant_id = current_setting('app.current_tenant_id')::UUID);
|
|
CREATE POLICY tenant_isolation_expenses ON hr.expenses
|
|
FOR ALL USING (tenant_id = current_setting('app.current_tenant_id')::UUID);
|
|
|
|
-- ============================================
|
|
-- 4. RESUME LINES (COR-065)
|
|
-- ============================================
|
|
|
|
CREATE TYPE hr.resume_line_type AS ENUM ('experience', 'education', 'certification', 'internal');
|
|
|
|
CREATE TABLE IF NOT EXISTS hr.employee_resume_lines (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
employee_id UUID NOT NULL REFERENCES hr.employees(id) ON DELETE CASCADE,
|
|
name VARCHAR(200) NOT NULL,
|
|
date_start DATE,
|
|
date_end DATE,
|
|
line_type hr.resume_line_type NOT NULL,
|
|
description TEXT,
|
|
-- Para experience
|
|
company_name VARCHAR(100),
|
|
job_title VARCHAR(100),
|
|
-- Para education
|
|
institution VARCHAR(100),
|
|
degree VARCHAR(100),
|
|
-- Para certification (importante en construcción)
|
|
certificate_number VARCHAR(50),
|
|
issuing_authority VARCHAR(100), -- STPS, NOM, etc.
|
|
expiry_date DATE,
|
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
-- ============================================
|
|
-- 5. PAYSLIP BASICS (COR-066)
|
|
-- ============================================
|
|
|
|
CREATE TYPE hr.payslip_status AS ENUM ('draft', 'verify', 'done', 'cancel');
|
|
|
|
CREATE TABLE IF NOT EXISTS hr.payslip_structures (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id),
|
|
name VARCHAR(100) NOT NULL,
|
|
code VARCHAR(50) NOT NULL,
|
|
is_active BOOLEAN DEFAULT TRUE,
|
|
-- Extensión construcción
|
|
tipo_pago VARCHAR(50), -- 'quincenal', 'semanal', 'destajo'
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
|
|
UNIQUE(tenant_id, code)
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS hr.payslips (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id),
|
|
company_id UUID REFERENCES core.companies(id),
|
|
employee_id UUID NOT NULL REFERENCES hr.employees(id),
|
|
contract_id UUID REFERENCES hr.contracts(id),
|
|
structure_id UUID REFERENCES hr.payslip_structures(id),
|
|
name VARCHAR(100) NOT NULL,
|
|
number VARCHAR(50),
|
|
status hr.payslip_status DEFAULT 'draft',
|
|
date_from DATE NOT NULL,
|
|
date_to DATE NOT NULL,
|
|
-- Extensión construcción
|
|
proyecto_id UUID, -- Nómina por proyecto
|
|
is_destajo BOOLEAN DEFAULT FALSE,
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS hr.payslip_lines (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
payslip_id UUID NOT NULL REFERENCES hr.payslips(id) ON DELETE CASCADE,
|
|
name VARCHAR(100) NOT NULL,
|
|
code VARCHAR(50) NOT NULL,
|
|
category VARCHAR(50) NOT NULL, -- 'basic', 'allowance', 'deduction', 'gross', 'net'
|
|
quantity DECIMAL(10,4) DEFAULT 1,
|
|
rate DECIMAL(18,4) DEFAULT 0,
|
|
amount DECIMAL(18,4) NOT NULL DEFAULT 0,
|
|
sequence INTEGER DEFAULT 10,
|
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
ALTER TABLE hr.payslip_structures ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE hr.payslips ENABLE ROW LEVEL SECURITY;
|
|
|
|
CREATE POLICY tenant_isolation_payslip_structures ON hr.payslip_structures
|
|
FOR ALL USING (tenant_id = current_setting('app.current_tenant_id')::UUID);
|
|
CREATE POLICY tenant_isolation_payslips ON hr.payslips
|
|
FOR ALL USING (tenant_id = current_setting('app.current_tenant_id')::UUID);
|
|
|
|
-- ============================================
|
|
-- 6. CAMPOS ADICIONALES EN EMPLOYEES (COR-061)
|
|
-- ============================================
|
|
|
|
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 (gafete)
|
|
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;
|
|
-- pin (para entrada)
|
|
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'hr' AND table_name = 'employees' AND column_name = 'pin') THEN
|
|
ALTER TABLE hr.employees ADD COLUMN pin VARCHAR(10);
|
|
END IF;
|
|
-- barcode (asistencia)
|
|
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'hr' AND table_name = 'employees' AND column_name = 'barcode') THEN
|
|
ALTER TABLE hr.employees ADD COLUMN barcode VARCHAR(50);
|
|
END IF;
|
|
-- vehicle
|
|
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'hr' AND table_name = 'employees' AND column_name = 'vehicle') THEN
|
|
ALTER TABLE hr.employees ADD COLUMN vehicle VARCHAR(100);
|
|
END IF;
|
|
-- vehicle_license_plate
|
|
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'hr' AND table_name = 'employees' AND column_name = 'vehicle_license_plate') THEN
|
|
ALTER TABLE hr.employees ADD COLUMN vehicle_license_plate VARCHAR(20);
|
|
END IF;
|
|
-- certificate (nivel educativo)
|
|
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'hr' AND table_name = 'employees' AND column_name = 'certificate') THEN
|
|
ALTER TABLE hr.employees ADD COLUMN certificate VARCHAR(50);
|
|
END IF;
|
|
END IF;
|
|
END $$;
|
|
```
|
|
|
|
**Líneas a agregar:** ~300
|
|
|
|
---
|
|
|
|
### 2.2 Modificar: 06-inventory-ext-schema-ddl.sql
|
|
|
|
**Agregar al final del archivo:**
|
|
|
|
```sql
|
|
-- ============================================
|
|
-- EXTENSIONES INVENTORY - ERP-Core FASE-8
|
|
-- COR-040 a COR-044
|
|
-- ============================================
|
|
|
|
-- ============================================
|
|
-- 1. PACKAGE TYPES (COR-040)
|
|
-- ============================================
|
|
|
|
CREATE TABLE IF NOT EXISTS inventory.package_types (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id),
|
|
name VARCHAR(100) NOT NULL,
|
|
sequence INTEGER DEFAULT 10,
|
|
height DECIMAL(10,2), -- cm
|
|
width DECIMAL(10,2),
|
|
length DECIMAL(10,2),
|
|
base_weight DECIMAL(10,4), -- kg
|
|
max_weight DECIMAL(10,4),
|
|
barcode VARCHAR(50),
|
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS inventory.packages (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id),
|
|
name VARCHAR(100) NOT NULL,
|
|
package_type_id UUID REFERENCES inventory.package_types(id),
|
|
shipping_weight DECIMAL(10,4),
|
|
location_id UUID REFERENCES inventory.locations(id),
|
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
-- RLS
|
|
ALTER TABLE inventory.package_types ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE inventory.packages ENABLE ROW LEVEL SECURITY;
|
|
|
|
CREATE POLICY tenant_isolation_package_types ON inventory.package_types
|
|
FOR ALL USING (tenant_id = current_setting('app.current_tenant_id')::UUID);
|
|
CREATE POLICY tenant_isolation_packages ON inventory.packages
|
|
FOR ALL USING (tenant_id = current_setting('app.current_tenant_id')::UUID);
|
|
|
|
-- ============================================
|
|
-- 2. STORAGE CATEGORIES (COR-042)
|
|
-- ============================================
|
|
|
|
CREATE TABLE IF NOT EXISTS inventory.storage_categories (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id),
|
|
name VARCHAR(100) NOT NULL,
|
|
max_weight DECIMAL(12,4),
|
|
allow_new_product VARCHAR(20) DEFAULT 'mixed',
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
|
|
CONSTRAINT chk_allow CHECK (allow_new_product IN ('empty', 'same', 'mixed'))
|
|
);
|
|
|
|
ALTER TABLE inventory.storage_categories ENABLE ROW LEVEL SECURITY;
|
|
|
|
CREATE POLICY tenant_isolation_storage_categories ON inventory.storage_categories
|
|
FOR ALL USING (tenant_id = current_setting('app.current_tenant_id')::UUID);
|
|
|
|
-- ============================================
|
|
-- 3. PUTAWAY RULES (COR-041)
|
|
-- ============================================
|
|
|
|
CREATE TABLE IF NOT EXISTS inventory.putaway_rules (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id),
|
|
product_id UUID REFERENCES inventory.products(id),
|
|
category_id UUID REFERENCES inventory.categories(id),
|
|
location_in_id UUID NOT NULL REFERENCES inventory.locations(id),
|
|
location_out_id UUID NOT NULL REFERENCES inventory.locations(id),
|
|
sequence INTEGER DEFAULT 10,
|
|
storage_category_id UUID REFERENCES inventory.storage_categories(id),
|
|
company_id UUID REFERENCES core.companies(id),
|
|
is_active BOOLEAN DEFAULT TRUE,
|
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
ALTER TABLE inventory.putaway_rules ENABLE ROW LEVEL SECURITY;
|
|
|
|
CREATE POLICY tenant_isolation_putaway_rules ON inventory.putaway_rules
|
|
FOR ALL USING (tenant_id = current_setting('app.current_tenant_id')::UUID);
|
|
|
|
-- ============================================
|
|
-- 4. REMOVAL STRATEGIES (COR-044)
|
|
-- ============================================
|
|
|
|
CREATE TABLE IF NOT EXISTS inventory.removal_strategies (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
code VARCHAR(20) NOT NULL UNIQUE,
|
|
name VARCHAR(50) NOT NULL,
|
|
description TEXT,
|
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
-- ============================================
|
|
-- 5. CAMPOS ADICIONALES EN PRODUCTS (COR-043)
|
|
-- ============================================
|
|
|
|
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;
|
|
-- 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 FALSE;
|
|
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;
|
|
-- volume
|
|
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'inventory' AND table_name = 'products' AND column_name = 'volume') THEN
|
|
ALTER TABLE inventory.products ADD COLUMN volume DECIMAL(10,4);
|
|
END IF;
|
|
-- weight
|
|
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'inventory' AND table_name = 'products' AND column_name = 'weight') THEN
|
|
ALTER TABLE inventory.products ADD COLUMN weight DECIMAL(10,4);
|
|
END IF;
|
|
-- hs_code (arancel)
|
|
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'inventory' AND table_name = 'products' AND column_name = 'hs_code') THEN
|
|
ALTER TABLE inventory.products ADD COLUMN hs_code VARCHAR(20);
|
|
END IF;
|
|
-- origin_country_id
|
|
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'inventory' AND table_name = 'products' AND column_name = 'origin_country_id') THEN
|
|
ALTER TABLE inventory.products ADD COLUMN origin_country_id UUID REFERENCES core.countries(id);
|
|
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;
|
|
END IF;
|
|
END $$;
|
|
```
|
|
|
|
**Líneas a agregar:** ~150
|
|
|
|
---
|
|
|
|
### 2.3 Modificar: 07-purchase-ext-schema-ddl.sql
|
|
|
|
**Agregar al final del archivo:**
|
|
|
|
```sql
|
|
-- ============================================
|
|
-- EXTENSIONES PURCHASE - ERP-Core FASE-8
|
|
-- COR-045 a COR-047
|
|
-- ============================================
|
|
|
|
-- ============================================
|
|
-- 1. PRODUCT SUPPLIERINFO (COR-045)
|
|
-- ============================================
|
|
|
|
CREATE TABLE IF NOT EXISTS purchase.product_supplierinfo (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id),
|
|
company_id UUID REFERENCES core.companies(id),
|
|
partner_id UUID NOT NULL REFERENCES core.partners(id),
|
|
product_id UUID REFERENCES inventory.products(id),
|
|
product_name VARCHAR(200),
|
|
product_code VARCHAR(50),
|
|
sequence INTEGER DEFAULT 10,
|
|
min_qty DECIMAL(12,4) DEFAULT 0,
|
|
price DECIMAL(18,4) NOT NULL,
|
|
currency_id UUID REFERENCES core.currencies(id),
|
|
delay INTEGER DEFAULT 1, -- días
|
|
date_start DATE,
|
|
date_end DATE,
|
|
-- Extensión construcción
|
|
proyecto_id UUID, -- Precio por proyecto
|
|
aplica_iva BOOLEAN DEFAULT TRUE,
|
|
flete_incluido BOOLEAN DEFAULT FALSE,
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
-- Índices
|
|
CREATE INDEX IF NOT EXISTS idx_supplierinfo_tenant ON purchase.product_supplierinfo(tenant_id);
|
|
CREATE INDEX IF NOT EXISTS idx_supplierinfo_partner ON purchase.product_supplierinfo(partner_id);
|
|
CREATE INDEX IF NOT EXISTS idx_supplierinfo_product ON purchase.product_supplierinfo(product_id);
|
|
|
|
-- RLS
|
|
ALTER TABLE purchase.product_supplierinfo ENABLE ROW LEVEL SECURITY;
|
|
|
|
CREATE POLICY tenant_isolation_supplierinfo ON purchase.product_supplierinfo
|
|
FOR ALL USING (tenant_id = current_setting('app.current_tenant_id')::UUID);
|
|
|
|
-- ============================================
|
|
-- 2. CAMPOS ADICIONALES EN ORDERS (COR-046)
|
|
-- ============================================
|
|
|
|
DO $$
|
|
BEGIN
|
|
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema = 'purchase' AND table_name = 'orders') THEN
|
|
-- origin
|
|
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'purchase' AND table_name = 'orders' AND column_name = 'origin') THEN
|
|
ALTER TABLE purchase.orders ADD COLUMN origin VARCHAR(200);
|
|
END IF;
|
|
-- partner_ref
|
|
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'purchase' AND table_name = 'orders' AND column_name = 'partner_ref') THEN
|
|
ALTER TABLE 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 = 'orders' AND column_name = 'date_approve') THEN
|
|
ALTER TABLE 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 = 'orders' AND column_name = 'receipt_status') THEN
|
|
ALTER TABLE purchase.orders ADD COLUMN receipt_status VARCHAR(20) DEFAULT 'pending';
|
|
END IF;
|
|
-- incoterm_id
|
|
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'purchase' AND table_name = 'orders' AND column_name = 'incoterm_id') THEN
|
|
ALTER TABLE purchase.orders ADD COLUMN incoterm_id UUID REFERENCES financial.incoterms(id);
|
|
END IF;
|
|
END IF;
|
|
END $$;
|
|
|
|
-- ============================================
|
|
-- 3. FUNCIÓN CREATE STOCK MOVES (COR-047)
|
|
-- ============================================
|
|
|
|
CREATE OR REPLACE FUNCTION purchase.action_create_stock_moves(p_order_id UUID)
|
|
RETURNS UUID AS $$
|
|
DECLARE
|
|
v_picking_id UUID;
|
|
v_tenant_id UUID;
|
|
v_partner_id UUID;
|
|
v_location_dest_id UUID;
|
|
v_location_src_id UUID;
|
|
v_line RECORD;
|
|
BEGIN
|
|
-- Obtener datos de la orden
|
|
SELECT tenant_id, partner_id
|
|
INTO v_tenant_id, v_partner_id
|
|
FROM purchase.orders WHERE id = p_order_id;
|
|
|
|
-- Obtener ubicaciones (supplier -> stock)
|
|
SELECT id INTO v_location_src_id
|
|
FROM inventory.locations
|
|
WHERE tenant_id = v_tenant_id AND usage = 'supplier' LIMIT 1;
|
|
|
|
SELECT id INTO v_location_dest_id
|
|
FROM inventory.locations
|
|
WHERE tenant_id = v_tenant_id AND usage = 'internal' LIMIT 1;
|
|
|
|
-- Crear picking de recepción
|
|
INSERT INTO inventory.stock_pickings (
|
|
tenant_id, picking_type, partner_id,
|
|
location_id, location_dest_id, origin,
|
|
status, scheduled_date
|
|
) VALUES (
|
|
v_tenant_id, 'incoming', v_partner_id,
|
|
v_location_src_id, v_location_dest_id,
|
|
(SELECT name FROM purchase.orders WHERE id = p_order_id),
|
|
'draft', CURRENT_DATE
|
|
) RETURNING id INTO v_picking_id;
|
|
|
|
-- Crear moves por cada línea
|
|
FOR v_line IN
|
|
SELECT product_id, quantity, price_unit
|
|
FROM purchase.order_lines
|
|
WHERE order_id = p_order_id
|
|
LOOP
|
|
INSERT INTO inventory.stock_moves (
|
|
tenant_id, picking_id, product_id,
|
|
product_qty, location_id, location_dest_id, status
|
|
) VALUES (
|
|
v_tenant_id, v_picking_id, v_line.product_id,
|
|
v_line.quantity, v_location_src_id, v_location_dest_id, 'draft'
|
|
);
|
|
END LOOP;
|
|
|
|
-- Actualizar status de orden
|
|
UPDATE purchase.orders
|
|
SET receipt_status = 'pending'
|
|
WHERE id = p_order_id;
|
|
|
|
RETURN v_picking_id;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
```
|
|
|
|
**Líneas a agregar:** ~130
|
|
|
|
---
|
|
|
|
## 3. Script de Migración Consolidado
|
|
|
|
### 3.1 Archivo: migrations/20260104_fase8_construccion.sql
|
|
|
|
```sql
|
|
-- ============================================
|
|
-- MIGRACIÓN FASE-8 - ERP CONSTRUCCIÓN
|
|
-- Fecha: 2026-01-04
|
|
-- Basado en: ERP-Core FASE-8
|
|
-- ============================================
|
|
|
|
BEGIN;
|
|
|
|
-- Verificación previa
|
|
DO $$
|
|
BEGIN
|
|
-- Verificar que existan schemas requeridos
|
|
IF NOT EXISTS (SELECT 1 FROM information_schema.schemata WHERE schema_name = 'construction') THEN
|
|
RAISE EXCEPTION 'Schema construction no existe';
|
|
END IF;
|
|
IF NOT EXISTS (SELECT 1 FROM information_schema.schemata WHERE schema_name = 'hr') THEN
|
|
RAISE EXCEPTION 'Schema hr no existe';
|
|
END IF;
|
|
END $$;
|
|
|
|
-- ============================================
|
|
-- FASE 1: EXTENSIONES FINANCIAL
|
|
-- ============================================
|
|
|
|
\echo 'Aplicando extensiones Financial...'
|
|
|
|
-- [Contenido de 08-financial-ext-schema-ddl.sql]
|
|
-- Se ejecuta el archivo completo
|
|
|
|
-- ============================================
|
|
-- FASE 2: EXTENSIONES PROJECTS/CONSTRUCTION
|
|
-- ============================================
|
|
|
|
\echo 'Aplicando extensiones Projects/Construction...'
|
|
|
|
-- [Contenido de 09-projects-ext-schema-ddl.sql]
|
|
-- Se ejecuta el archivo completo
|
|
|
|
-- ============================================
|
|
-- FASE 3: EXTENSIONES HR
|
|
-- ============================================
|
|
|
|
\echo 'Aplicando extensiones HR...'
|
|
|
|
-- [Agregados a 02-hr-schema-ddl.sql]
|
|
|
|
-- ============================================
|
|
-- FASE 4: EXTENSIONES INVENTORY
|
|
-- ============================================
|
|
|
|
\echo 'Aplicando extensiones Inventory...'
|
|
|
|
-- [Agregados a 06-inventory-ext-schema-ddl.sql]
|
|
|
|
-- ============================================
|
|
-- FASE 5: EXTENSIONES PURCHASE
|
|
-- ============================================
|
|
|
|
\echo 'Aplicando extensiones Purchase...'
|
|
|
|
-- [Agregados a 07-purchase-ext-schema-ddl.sql]
|
|
|
|
-- ============================================
|
|
-- FASE 6: SEED DATA
|
|
-- ============================================
|
|
|
|
\echo 'Insertando seed data...'
|
|
|
|
-- Incoterms estándar
|
|
INSERT INTO financial.incoterms (code, name) VALUES
|
|
('EXW', 'Ex Works'),
|
|
('FCA', 'Free Carrier'),
|
|
('CPT', 'Carriage Paid To'),
|
|
('CIP', 'Carriage and Insurance Paid To'),
|
|
('DAP', 'Delivered at Place'),
|
|
('DPU', 'Delivered at Place Unloaded'),
|
|
('DDP', 'Delivered Duty Paid'),
|
|
('FAS', 'Free Alongside Ship'),
|
|
('FOB', 'Free On Board'),
|
|
('CFR', 'Cost and Freight'),
|
|
('CIF', 'Cost, Insurance and Freight')
|
|
ON CONFLICT (code) DO NOTHING;
|
|
|
|
-- Removal strategies
|
|
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'),
|
|
('closest', 'Closest Location', 'Salida por ubicación más cercana')
|
|
ON CONFLICT (code) DO NOTHING;
|
|
|
|
-- ============================================
|
|
-- VALIDACIÓN
|
|
-- ============================================
|
|
|
|
\echo 'Validando migración...'
|
|
|
|
DO $$
|
|
DECLARE
|
|
v_count INTEGER;
|
|
BEGIN
|
|
-- Verificar tablas creadas
|
|
SELECT COUNT(*) INTO v_count
|
|
FROM information_schema.tables
|
|
WHERE table_schema IN ('financial', 'inventory', 'purchase', 'hr', 'projects', 'construction')
|
|
AND table_name IN (
|
|
'payment_term_lines', 'incoterms', 'payment_methods',
|
|
'reconcile_models', 'reconcile_model_lines',
|
|
'package_types', 'packages', 'storage_categories',
|
|
'putaway_rules', 'removal_strategies',
|
|
'product_supplierinfo',
|
|
'collaborators', 'ratings', 'avance_programado',
|
|
'work_locations', 'ubicaciones_obra',
|
|
'skill_types', 'skills', 'skill_levels', 'employee_skills',
|
|
'expense_sheets', 'expenses', 'employee_resume_lines',
|
|
'payslip_structures', 'payslips', 'payslip_lines'
|
|
);
|
|
|
|
IF v_count < 20 THEN
|
|
RAISE WARNING 'Solo se encontraron % de 24 tablas esperadas', v_count;
|
|
ELSE
|
|
RAISE NOTICE 'Validación exitosa: % tablas creadas', v_count;
|
|
END IF;
|
|
END $$;
|
|
|
|
COMMIT;
|
|
|
|
\echo 'Migración FASE-8 completada exitosamente'
|
|
```
|
|
|
|
---
|
|
|
|
## 4. Seed Data Específico de Construcción
|
|
|
|
### 4.1 Archivo: seeds/construccion-skill-types.sql
|
|
|
|
```sql
|
|
-- ============================================
|
|
-- SEED DATA: HABILIDADES DE CONSTRUCCIÓN
|
|
-- ============================================
|
|
|
|
-- Tipos de habilidad
|
|
INSERT INTO hr.skill_types (tenant_id, name) VALUES
|
|
(current_setting('app.current_tenant_id')::UUID, 'Albañilería'),
|
|
(current_setting('app.current_tenant_id')::UUID, 'Plomería'),
|
|
(current_setting('app.current_tenant_id')::UUID, 'Electricidad'),
|
|
(current_setting('app.current_tenant_id')::UUID, 'Herrería'),
|
|
(current_setting('app.current_tenant_id')::UUID, 'Carpintería'),
|
|
(current_setting('app.current_tenant_id')::UUID, 'Acabados'),
|
|
(current_setting('app.current_tenant_id')::UUID, 'Maquinaria Pesada'),
|
|
(current_setting('app.current_tenant_id')::UUID, 'Topografía'),
|
|
(current_setting('app.current_tenant_id')::UUID, 'Soldadura');
|
|
|
|
-- Niveles de habilidad (aplicables a todos los tipos)
|
|
INSERT INTO hr.skill_levels (tenant_id, skill_type_id, name, level)
|
|
SELECT
|
|
current_setting('app.current_tenant_id')::UUID,
|
|
st.id,
|
|
l.name,
|
|
l.level
|
|
FROM hr.skill_types st
|
|
CROSS JOIN (VALUES
|
|
('Ayudante', 1),
|
|
('Oficial', 2),
|
|
('Maestro', 3),
|
|
('Especialista', 4)
|
|
) AS l(name, level)
|
|
WHERE st.tenant_id = current_setting('app.current_tenant_id')::UUID;
|
|
|
|
-- Habilidades específicas por tipo
|
|
-- Albañilería
|
|
INSERT INTO hr.skills (tenant_id, skill_type_id, name)
|
|
SELECT
|
|
current_setting('app.current_tenant_id')::UUID,
|
|
id,
|
|
unnest(ARRAY['Cimentación', 'Muros de block', 'Losas', 'Castillos y dalas', 'Firmes', 'Aplanados'])
|
|
FROM hr.skill_types
|
|
WHERE name = 'Albañilería' AND tenant_id = current_setting('app.current_tenant_id')::UUID;
|
|
|
|
-- Plomería
|
|
INSERT INTO hr.skills (tenant_id, skill_type_id, name)
|
|
SELECT
|
|
current_setting('app.current_tenant_id')::UUID,
|
|
id,
|
|
unnest(ARRAY['Instalación hidráulica', 'Instalación sanitaria', 'Gas', 'Tinaco y cisterna', 'Calentadores'])
|
|
FROM hr.skill_types
|
|
WHERE name = 'Plomería' AND tenant_id = current_setting('app.current_tenant_id')::UUID;
|
|
|
|
-- Electricidad
|
|
INSERT INTO hr.skills (tenant_id, skill_type_id, name)
|
|
SELECT
|
|
current_setting('app.current_tenant_id')::UUID,
|
|
id,
|
|
unnest(ARRAY['Instalación residencial', 'Tableros', 'Acometida CFE', 'Tierra física', 'Iluminación'])
|
|
FROM hr.skill_types
|
|
WHERE name = 'Electricidad' AND tenant_id = current_setting('app.current_tenant_id')::UUID;
|
|
```
|
|
|
|
### 4.2 Archivo: seeds/construccion-storage-categories.sql
|
|
|
|
```sql
|
|
-- ============================================
|
|
-- SEED DATA: CATEGORÍAS DE ALMACÉN CONSTRUCCIÓN
|
|
-- ============================================
|
|
|
|
INSERT INTO inventory.storage_categories (tenant_id, name, max_weight, allow_new_product) VALUES
|
|
(current_setting('app.current_tenant_id')::UUID, 'Área Techada', 10000.0, 'mixed'),
|
|
(current_setting('app.current_tenant_id')::UUID, 'Área Descubierta', 50000.0, 'mixed'),
|
|
(current_setting('app.current_tenant_id')::UUID, 'Bodega Cerrada', 5000.0, 'mixed'),
|
|
(current_setting('app.current_tenant_id')::UUID, 'Caseta Herramienta', 500.0, 'same'),
|
|
(current_setting('app.current_tenant_id')::UUID, 'Área Inflamables', 200.0, 'same');
|
|
```
|
|
|
|
### 4.3 Archivo: seeds/construccion-package-types.sql
|
|
|
|
```sql
|
|
-- ============================================
|
|
-- SEED DATA: TIPOS DE PAQUETE CONSTRUCCIÓN
|
|
-- ============================================
|
|
|
|
INSERT INTO inventory.package_types (tenant_id, name, height, width, length, base_weight, max_weight, sequence) VALUES
|
|
(current_setting('app.current_tenant_id')::UUID, 'Tarima Block', 150.0, 100.0, 100.0, 5.0, 500.0, 10),
|
|
(current_setting('app.current_tenant_id')::UUID, 'Paquete Varilla', 600.0, 30.0, 30.0, 2.0, 1000.0, 20),
|
|
(current_setting('app.current_tenant_id')::UUID, 'Rollo Cable', 50.0, 50.0, 20.0, 0.5, 50.0, 30),
|
|
(current_setting('app.current_tenant_id')::UUID, 'Saco Cemento', 60.0, 40.0, 15.0, 0.2, 50.0, 40),
|
|
(current_setting('app.current_tenant_id')::UUID, 'Cubeta Pintura', 40.0, 30.0, 30.0, 0.3, 25.0, 50),
|
|
(current_setting('app.current_tenant_id')::UUID, 'Caja Herrajes', 40.0, 30.0, 20.0, 0.5, 30.0, 60);
|
|
```
|
|
|
|
### 4.4 Archivo: seeds/construccion-payment-methods.sql
|
|
|
|
```sql
|
|
-- ============================================
|
|
-- SEED DATA: MÉTODOS DE PAGO CONSTRUCCIÓN
|
|
-- ============================================
|
|
|
|
INSERT INTO financial.payment_methods (tenant_id, name, code, payment_type) VALUES
|
|
(current_setting('app.current_tenant_id')::UUID, 'Anticipo de Obra', 'anticipo_obra', 'outbound'),
|
|
(current_setting('app.current_tenant_id')::UUID, 'Pago Estimación', 'pago_estimacion', 'outbound'),
|
|
(current_setting('app.current_tenant_id')::UUID, 'Pago Destajo', 'pago_destajo', 'outbound'),
|
|
(current_setting('app.current_tenant_id')::UUID, 'Pago Finiquito', 'pago_finiquito', 'outbound'),
|
|
(current_setting('app.current_tenant_id')::UUID, 'Retención 5%', 'retencion_5', 'outbound'),
|
|
(current_setting('app.current_tenant_id')::UUID, 'Cobro Cliente', 'cobro_cliente', 'inbound'),
|
|
(current_setting('app.current_tenant_id')::UUID, 'Anticipo Cliente', 'anticipo_cliente', 'inbound');
|
|
```
|
|
|
|
---
|
|
|
|
## 5. Resumen del Plan
|
|
|
|
### 5.1 Archivos a Crear
|
|
|
|
| Archivo | Líneas | Contenido |
|
|
|---------|--------|-----------|
|
|
| 08-financial-ext-schema-ddl.sql | ~180 | 5 tablas, 2 ENUMs |
|
|
| 09-projects-ext-schema-ddl.sql | ~280 | 3 tablas, 2 funciones |
|
|
| migrations/20260104_fase8_construccion.sql | ~200 | Migración consolidada |
|
|
| seeds/construccion-skill-types.sql | ~60 | Habilidades construcción |
|
|
| seeds/construccion-storage-categories.sql | ~15 | Categorías almacén |
|
|
| seeds/construccion-package-types.sql | ~20 | Tipos de paquete |
|
|
| seeds/construccion-payment-methods.sql | ~15 | Métodos pago obra |
|
|
| **TOTAL** | **~770** | |
|
|
|
|
### 5.2 Archivos a Modificar
|
|
|
|
| Archivo | Líneas a agregar | Contenido |
|
|
|---------|-----------------|-----------|
|
|
| 02-hr-schema-ddl.sql | ~300 | 11 tablas, 3 ENUMs |
|
|
| 06-inventory-ext-schema-ddl.sql | ~150 | 5 tablas |
|
|
| 07-purchase-ext-schema-ddl.sql | ~130 | 1 tabla, 1 función |
|
|
| **TOTAL** | **~580** | |
|
|
|
|
### 5.3 Documentación a Actualizar
|
|
|
|
| Documento | Cambio |
|
|
|-----------|--------|
|
|
| HERENCIA-ERP-CORE.md | Agregar referencia FASE-8 |
|
|
| DEPENDENCIAS-ERP-CORE.yml | Actualizar versión |
|
|
| DATABASE_INVENTORY.yml | Agregar 24 nuevas tablas |
|
|
|
|
---
|
|
|
|
## 6. Orden de Ejecución
|
|
|
|
1. **Paso 1:** Crear 08-financial-ext-schema-ddl.sql
|
|
2. **Paso 2:** Crear 09-projects-ext-schema-ddl.sql
|
|
3. **Paso 3:** Modificar 02-hr-schema-ddl.sql
|
|
4. **Paso 4:** Modificar 06-inventory-ext-schema-ddl.sql
|
|
5. **Paso 5:** Modificar 07-purchase-ext-schema-ddl.sql
|
|
6. **Paso 6:** Crear archivos seed
|
|
7. **Paso 7:** Crear script de migración
|
|
8. **Paso 8:** Actualizar documentación
|
|
|
|
---
|
|
|
|
**Estado:** FASE 3 COMPLETADA
|
|
**Siguiente:** FASE 4 - Validación del Plan
|
|
**Fecha:** 2026-01-04
|