erp-core-database-v2/ddl/16-partners.sql
Adrian Flores Cortes ac54edc855 [SPRINT-2-COHERENCIA] feat: Add missing DDL tables and fix numbering
- Add inventory.lots table (coherence with lot.entity.ts)
- Add inventory.pickings table with ENUMs (coherence with picking.entity.ts)
- Add partners.partner_tax_info table (coherence with partner-tax-info.entity.ts)
- Add partners.partner_segments table (coherence with partner-segment.entity.ts)
- Rename 21-fiscal-catalogs.sql to 26-fiscal-catalogs.sql (fix duplicate with 21-inventory.sql)
- Remove duplicate 25-fiscal-catalogs.sql

Gaps resolved:
- GAP-P1-001: lots DDL added
- GAP-P1-002: pickings DDL added
- GAP-P1-006: partner_tax_info DDL added
- GAP-P1-007: partner_segments DDL added
- GAP-P1-008: DDL numbering fixed

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-24 08:54:32 -06:00

316 lines
12 KiB
SQL

-- =============================================================
-- ARCHIVO: 16-partners.sql
-- DESCRIPCION: Partners (clientes, proveedores, contactos)
-- VERSION: 1.0.0
-- PROYECTO: ERP-Core V2
-- FECHA: 2026-01-13
-- =============================================================
-- =====================
-- SCHEMA: partners
-- =====================
CREATE SCHEMA IF NOT EXISTS partners;
-- =====================
-- TABLA: partners
-- Clientes, proveedores, y otros socios comerciales
-- =====================
CREATE TABLE IF NOT EXISTS partners.partners (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
-- Identificacion
code VARCHAR(30) NOT NULL,
name VARCHAR(200) NOT NULL,
display_name VARCHAR(200),
-- Tipo de partner
partner_type VARCHAR(20) NOT NULL DEFAULT 'customer', -- customer, supplier, both, contact
-- Datos fiscales
tax_id VARCHAR(50), -- RFC en Mexico
tax_id_type VARCHAR(20), -- rfc_moral, rfc_fisica, extranjero
legal_name VARCHAR(200),
-- Contacto principal
email VARCHAR(255),
phone VARCHAR(30),
mobile VARCHAR(30),
website VARCHAR(255),
-- Credito y pagos
credit_limit DECIMAL(15, 2) DEFAULT 0,
payment_term_days INTEGER DEFAULT 0,
payment_method VARCHAR(50), -- cash, transfer, credit_card, check
-- Clasificacion
category VARCHAR(50), -- retail, wholesale, government, etc.
tags TEXT[] DEFAULT '{}',
-- Estado
is_active BOOLEAN DEFAULT TRUE,
is_verified BOOLEAN DEFAULT FALSE,
verified_at TIMESTAMPTZ,
-- Configuracion
settings JSONB DEFAULT '{}',
-- Ejemplo: {"send_reminders": true, "preferred_contact": "email"}
-- Notas
notes TEXT,
-- Metadata
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
created_by UUID REFERENCES auth.users(id),
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
updated_by UUID REFERENCES auth.users(id),
deleted_at TIMESTAMPTZ,
UNIQUE(tenant_id, code)
);
-- Indices para partners
CREATE INDEX IF NOT EXISTS idx_partners_tenant ON partners.partners(tenant_id);
CREATE INDEX IF NOT EXISTS idx_partners_code ON partners.partners(code);
CREATE INDEX IF NOT EXISTS idx_partners_type ON partners.partners(partner_type);
CREATE INDEX IF NOT EXISTS idx_partners_tax_id ON partners.partners(tax_id);
CREATE INDEX IF NOT EXISTS idx_partners_active ON partners.partners(is_active) WHERE is_active = TRUE;
CREATE INDEX IF NOT EXISTS idx_partners_email ON partners.partners(email);
CREATE INDEX IF NOT EXISTS idx_partners_name ON partners.partners USING gin(to_tsvector('spanish', name));
-- =====================
-- TABLA: partner_addresses
-- Direcciones de partners (facturacion, envio, etc.)
-- =====================
CREATE TABLE IF NOT EXISTS partners.partner_addresses (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
partner_id UUID NOT NULL REFERENCES partners.partners(id) ON DELETE CASCADE,
-- Tipo de direccion
address_type VARCHAR(20) NOT NULL DEFAULT 'billing', -- billing, shipping, main, other
-- Direccion
address_line1 VARCHAR(200) NOT NULL,
address_line2 VARCHAR(200),
city VARCHAR(100) NOT NULL,
state VARCHAR(100),
postal_code VARCHAR(20),
country VARCHAR(3) DEFAULT 'MEX',
-- Contacto en esta direccion
contact_name VARCHAR(100),
contact_phone VARCHAR(30),
contact_email VARCHAR(255),
-- Referencia
reference TEXT,
-- Geolocalizacion
latitude DECIMAL(10, 8),
longitude DECIMAL(11, 8),
-- Estado
is_default BOOLEAN DEFAULT FALSE,
is_active BOOLEAN DEFAULT TRUE,
-- Metadata
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMPTZ
);
-- Indices para partner_addresses
CREATE INDEX IF NOT EXISTS idx_partner_addresses_partner ON partners.partner_addresses(partner_id);
CREATE INDEX IF NOT EXISTS idx_partner_addresses_type ON partners.partner_addresses(address_type);
CREATE INDEX IF NOT EXISTS idx_partner_addresses_default ON partners.partner_addresses(partner_id, is_default) WHERE is_default = TRUE;
-- =====================
-- TABLA: partner_contacts
-- Contactos individuales de un partner
-- =====================
CREATE TABLE IF NOT EXISTS partners.partner_contacts (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
partner_id UUID NOT NULL REFERENCES partners.partners(id) ON DELETE CASCADE,
-- Datos personales
name VARCHAR(200) NOT NULL,
job_title VARCHAR(100),
department VARCHAR(100),
-- Contacto
email VARCHAR(255),
phone VARCHAR(30),
mobile VARCHAR(30),
-- Rol
contact_type VARCHAR(20) DEFAULT 'general', -- general, billing, purchasing, sales, technical
is_primary BOOLEAN DEFAULT FALSE,
-- Estado
is_active BOOLEAN DEFAULT TRUE,
-- Notas
notes TEXT,
-- Metadata
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMPTZ
);
-- Indices para partner_contacts
CREATE INDEX IF NOT EXISTS idx_partner_contacts_partner ON partners.partner_contacts(partner_id);
CREATE INDEX IF NOT EXISTS idx_partner_contacts_type ON partners.partner_contacts(contact_type);
CREATE INDEX IF NOT EXISTS idx_partner_contacts_primary ON partners.partner_contacts(partner_id, is_primary) WHERE is_primary = TRUE;
CREATE INDEX IF NOT EXISTS idx_partner_contacts_email ON partners.partner_contacts(email);
-- =====================
-- TABLA: partner_bank_accounts
-- Cuentas bancarias de partners
-- =====================
CREATE TABLE IF NOT EXISTS partners.partner_bank_accounts (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
partner_id UUID NOT NULL REFERENCES partners.partners(id) ON DELETE CASCADE,
-- Datos bancarios
bank_name VARCHAR(100) NOT NULL,
account_number VARCHAR(50),
clabe VARCHAR(20), -- CLABE para Mexico
swift_code VARCHAR(20),
iban VARCHAR(50),
-- Titular
account_holder VARCHAR(200),
-- Estado
is_default BOOLEAN DEFAULT FALSE,
is_active BOOLEAN DEFAULT TRUE,
is_verified BOOLEAN DEFAULT FALSE,
-- Metadata
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMPTZ
);
-- Indices para partner_bank_accounts
CREATE INDEX IF NOT EXISTS idx_partner_bank_accounts_partner ON partners.partner_bank_accounts(partner_id);
CREATE INDEX IF NOT EXISTS idx_partner_bank_accounts_default ON partners.partner_bank_accounts(partner_id, is_default) WHERE is_default = TRUE;
-- =====================
-- COMENTARIOS
-- =====================
COMMENT ON TABLE partners.partners IS 'Clientes, proveedores y otros socios comerciales del negocio';
COMMENT ON COLUMN partners.partners.partner_type IS 'Tipo: customer (cliente), supplier (proveedor), both (ambos), contact (contacto)';
COMMENT ON COLUMN partners.partners.tax_id IS 'Identificacion fiscal (RFC en Mexico)';
COMMENT ON COLUMN partners.partners.credit_limit IS 'Limite de credito en moneda local';
COMMENT ON COLUMN partners.partners.payment_term_days IS 'Dias de plazo para pago';
COMMENT ON TABLE partners.partner_addresses IS 'Direcciones asociadas a un partner (facturacion, envio, etc.)';
COMMENT ON COLUMN partners.partner_addresses.address_type IS 'Tipo: billing, shipping, main, other';
COMMENT ON TABLE partners.partner_contacts IS 'Personas de contacto individuales de un partner';
COMMENT ON COLUMN partners.partner_contacts.contact_type IS 'Rol: general, billing, purchasing, sales, technical';
COMMENT ON TABLE partners.partner_bank_accounts IS 'Cuentas bancarias de partners para pagos/cobros';
-- =====================
-- TABLA: partner_tax_info
-- Informacion fiscal extendida para facturacion y cumplimiento
-- Coherencia: partner-tax-info.entity.ts
-- =====================
CREATE TABLE IF NOT EXISTS partners.partner_tax_info (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
partner_id UUID NOT NULL REFERENCES partners.partners(id) ON DELETE CASCADE,
-- Identificacion fiscal
tax_id_type VARCHAR(20), -- RFC, CURP, EIN, VAT
tax_id_country VARCHAR(3) DEFAULT 'MEX',
-- SAT (Mexico) especifico
sat_regime VARCHAR(10), -- 601, 603, 612, etc.
sat_regime_name VARCHAR(200),
cfdi_use VARCHAR(10), -- G01, G02, G03, etc.
cfdi_use_name VARCHAR(200),
fiscal_zip_code VARCHAR(10),
-- Retenciones
withholding_isr DECIMAL(5, 2) DEFAULT 0,
withholding_iva DECIMAL(5, 2) DEFAULT 0,
-- Validacion
is_verified BOOLEAN DEFAULT FALSE,
verified_at TIMESTAMPTZ,
verification_source VARCHAR(50), -- SAT, MANUAL, API
-- Metadata
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);
-- Indices para partner_tax_info
CREATE INDEX IF NOT EXISTS idx_partner_tax_info_partner ON partners.partner_tax_info(partner_id);
CREATE INDEX IF NOT EXISTS idx_partner_tax_info_regime ON partners.partner_tax_info(sat_regime);
CREATE INDEX IF NOT EXISTS idx_partner_tax_info_verified ON partners.partner_tax_info(is_verified) WHERE is_verified = TRUE;
-- =====================
-- TABLA: partner_segments
-- Segmentos de clientes/proveedores para agrupacion y analytics
-- Coherencia: partner-segment.entity.ts
-- =====================
CREATE TABLE IF NOT EXISTS partners.partner_segments (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
-- Identificacion
code VARCHAR(30) NOT NULL,
name VARCHAR(100) NOT NULL,
description TEXT,
-- Tipo de segmento
segment_type VARCHAR(20) DEFAULT 'customer', -- customer, supplier, both
-- Estilo visual
color VARCHAR(20),
icon VARCHAR(50),
-- Reglas de auto-asignacion (JSON)
rules JSONB,
-- Beneficios/condiciones
default_discount DECIMAL(5, 2) DEFAULT 0,
default_payment_terms INTEGER DEFAULT 0,
priority INTEGER DEFAULT 0,
-- Estado
is_active BOOLEAN DEFAULT TRUE,
sort_order INTEGER DEFAULT 0,
-- Metadata
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
created_by UUID REFERENCES auth.users(id),
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
updated_by UUID REFERENCES auth.users(id),
UNIQUE(tenant_id, code)
);
-- Indices para partner_segments
CREATE INDEX IF NOT EXISTS idx_partner_segments_tenant ON partners.partner_segments(tenant_id);
CREATE INDEX IF NOT EXISTS idx_partner_segments_code ON partners.partner_segments(code);
CREATE INDEX IF NOT EXISTS idx_partner_segments_type ON partners.partner_segments(segment_type);
CREATE INDEX IF NOT EXISTS idx_partner_segments_active ON partners.partner_segments(is_active) WHERE is_active = TRUE;
-- =====================
-- COMENTARIOS ADICIONALES
-- =====================
COMMENT ON TABLE partners.partner_tax_info IS 'Informacion fiscal extendida para cumplimiento de facturacion electronica (CFDI en Mexico)';
COMMENT ON COLUMN partners.partner_tax_info.sat_regime IS 'Regimen fiscal SAT (ej: 601-General de Ley, 612-Persona Fisica)';
COMMENT ON COLUMN partners.partner_tax_info.cfdi_use IS 'Uso del CFDI (ej: G01-Adquisicion de mercancias, G03-Gastos en general)';
COMMENT ON COLUMN partners.partner_tax_info.withholding_isr IS 'Porcentaje de retencion de ISR';
COMMENT ON COLUMN partners.partner_tax_info.withholding_iva IS 'Porcentaje de retencion de IVA';
COMMENT ON TABLE partners.partner_segments IS 'Segmentos para clasificar y agrupar partners (VIP, Mayorista, Local, etc.)';
COMMENT ON COLUMN partners.partner_segments.rules IS 'Reglas JSON para auto-asignacion de partners al segmento';
COMMENT ON COLUMN partners.partner_segments.default_discount IS 'Descuento por defecto para partners en este segmento';