Initial commit - Horux Despachos NL
This commit is contained in:
244
apps/api/src/migrations/tenant/001_initial_schema.sql
Normal file
244
apps/api/src/migrations/tenant/001_initial_schema.sql
Normal file
@@ -0,0 +1,244 @@
|
||||
-- Migration: 001_initial_schema
|
||||
-- Description: Full initial DDL for tenant databases (horux_<rfc>)
|
||||
-- Created: 2026-04-13
|
||||
-- Tables: rfcs, bancos, cfdis, cfdi_conceptos, conciliaciones, alertas, recordatorios
|
||||
|
||||
-- Extensions
|
||||
CREATE EXTENSION IF NOT EXISTS pg_trgm;
|
||||
|
||||
-- Tables
|
||||
|
||||
CREATE TABLE IF NOT EXISTS rfcs (
|
||||
id SERIAL PRIMARY KEY,
|
||||
rfc VARCHAR(14) UNIQUE NOT NULL,
|
||||
razon_social VARCHAR(255),
|
||||
regimen_fiscal VARCHAR(3),
|
||||
codigo_postal VARCHAR(5)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS bancos (
|
||||
id SERIAL PRIMARY KEY,
|
||||
banco VARCHAR(100) NOT NULL,
|
||||
terminacion_cuenta VARCHAR(4) NOT NULL,
|
||||
creado_en TIMESTAMP DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS cfdis (
|
||||
id SERIAL PRIMARY KEY,
|
||||
year VARCHAR(4),
|
||||
month VARCHAR(2),
|
||||
type VARCHAR(10),
|
||||
uuid VARCHAR(36) UNIQUE,
|
||||
serie VARCHAR(50),
|
||||
folio VARCHAR(50),
|
||||
status VARCHAR(20),
|
||||
fecha_emision TIMESTAMP,
|
||||
rfc_emisor_id INTEGER REFERENCES rfcs(id),
|
||||
rfc_emisor VARCHAR(13),
|
||||
nombre_emisor VARCHAR(255),
|
||||
rfc_receptor_id INTEGER REFERENCES rfcs(id),
|
||||
rfc_receptor VARCHAR(13),
|
||||
nombre_receptor VARCHAR(255),
|
||||
subtotal NUMERIC(18,4),
|
||||
subtotal_mxn NUMERIC(18,4),
|
||||
descuento NUMERIC(18,4),
|
||||
descuento_mxn NUMERIC(18,4),
|
||||
total NUMERIC(18,4),
|
||||
total_mxn NUMERIC(18,4),
|
||||
saldo_insoluto TEXT,
|
||||
moneda VARCHAR(3),
|
||||
tipo_cambio NUMERIC(18,6),
|
||||
tipo_comprobante VARCHAR(1),
|
||||
metodo_pago VARCHAR(3),
|
||||
forma_pago VARCHAR(2),
|
||||
uso_cfdi VARCHAR(5),
|
||||
pac VARCHAR(13),
|
||||
fecha_cert_sat TIMESTAMP,
|
||||
fecha_cancelacion TIMESTAMP,
|
||||
uuid_relacionado TEXT,
|
||||
isr_retencion NUMERIC(18,4),
|
||||
isr_retencion_mxn NUMERIC(18,4),
|
||||
iva_traslado NUMERIC(18,4),
|
||||
iva_traslado_mxn NUMERIC(18,4),
|
||||
iva_retencion NUMERIC(18,4),
|
||||
iva_retencion_mxn NUMERIC(18,4),
|
||||
ieps_traslado NUMERIC(18,4),
|
||||
ieps_traslado_mxn NUMERIC(18,4),
|
||||
ieps_retencion NUMERIC(18,4),
|
||||
ieps_retencion_mxn NUMERIC(18,4),
|
||||
impuestos_locales_trasladado NUMERIC(18,4),
|
||||
impuestos_locales_trasladado_mxn NUMERIC(18,4),
|
||||
impuestos_locales_retenidos NUMERIC(18,4),
|
||||
impuestos_locales_retenidos_mxn NUMERIC(18,4),
|
||||
monto_pago NUMERIC(18,4),
|
||||
monto_pago_mxn NUMERIC(18,4),
|
||||
fecha_pago_p TIMESTAMP,
|
||||
num_parcialidad TEXT,
|
||||
isr_retencion_pago NUMERIC(18,4),
|
||||
isr_retencion_pago_mxn NUMERIC(18,4),
|
||||
iva_traslado_pago NUMERIC(18,4),
|
||||
iva_traslado_pago_mxn NUMERIC(18,4),
|
||||
iva_retencion_pago NUMERIC(18,4),
|
||||
iva_retencion_pago_mxn NUMERIC(18,4),
|
||||
ieps_traslado_pago NUMERIC(18,4),
|
||||
ieps_traslado_pago_mxn NUMERIC(18,4),
|
||||
ieps_retencion_pago NUMERIC(18,4),
|
||||
ieps_retencion_pago_mxn NUMERIC(18,4),
|
||||
saldo_pendiente NUMERIC(18,4),
|
||||
saldo_pendiente_mxn NUMERIC(18,4),
|
||||
fecha_liquidacion TIMESTAMP,
|
||||
fecha_pago DATE,
|
||||
fecha_inicial_pago DATE,
|
||||
fecha_final_pago DATE,
|
||||
num_dias_pagados NUMERIC(10,2),
|
||||
num_seguro_social VARCHAR(50),
|
||||
puesto VARCHAR(255),
|
||||
salario_base_cot_apor NUMERIC(18,4),
|
||||
salario_base_cot_apor_mxn NUMERIC(18,4),
|
||||
salario_diario_integrado NUMERIC(18,4),
|
||||
salario_diario_integrado_mxn NUMERIC(18,4),
|
||||
total_percepciones NUMERIC(18,4),
|
||||
total_percepciones_mxn NUMERIC(18,4),
|
||||
total_deducciones NUMERIC(18,4),
|
||||
total_deducciones_mxn NUMERIC(18,4),
|
||||
imp_retenidos_nomina NUMERIC(18,4),
|
||||
imp_retenidos_nomina_mxn NUMERIC(18,4),
|
||||
otras_deducciones_nomina NUMERIC(18,4),
|
||||
otras_deducciones_nomina_mxn NUMERIC(18,4),
|
||||
subsidio_causado NUMERIC(18,4),
|
||||
subsidio_causado_mxn NUMERIC(18,4),
|
||||
conciliado VARCHAR(50),
|
||||
id_conciliacion INTEGER,
|
||||
xml_url TEXT,
|
||||
pdf_url TEXT,
|
||||
xml_original TEXT,
|
||||
last_sat_sync TIMESTAMP,
|
||||
sat_sync_job_id UUID,
|
||||
source VARCHAR(20) DEFAULT 'manual',
|
||||
facturapi_id VARCHAR(50),
|
||||
regimen_fiscal_emisor VARCHAR(3),
|
||||
regimen_fiscal_receptor VARCHAR(3),
|
||||
creado_en TIMESTAMP DEFAULT NOW(),
|
||||
actualizado_en TIMESTAMP DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS cfdi_conceptos (
|
||||
id SERIAL PRIMARY KEY,
|
||||
cfdi_id INTEGER REFERENCES cfdis(id) ON DELETE CASCADE,
|
||||
clave_prod_serv VARCHAR(10),
|
||||
no_identificacion VARCHAR(100),
|
||||
descripcion TEXT,
|
||||
cantidad NUMERIC(18,4),
|
||||
clave_unidad VARCHAR(10),
|
||||
unidad VARCHAR(100),
|
||||
valor_unitario NUMERIC(18,4),
|
||||
valor_unitario_mxn NUMERIC(18,4),
|
||||
importe NUMERIC(18,4),
|
||||
importe_mxn NUMERIC(18,4),
|
||||
descuento NUMERIC(18,4),
|
||||
descuento_mxn NUMERIC(18,4),
|
||||
isr_retencion NUMERIC(18,4),
|
||||
isr_retencion_mxn NUMERIC(18,4),
|
||||
iva_traslado NUMERIC(18,4),
|
||||
iva_traslado_mxn NUMERIC(18,4),
|
||||
iva_retencion NUMERIC(18,4),
|
||||
iva_retencion_mxn NUMERIC(18,4),
|
||||
ieps_traslado NUMERIC(18,4),
|
||||
ieps_traslado_mxn NUMERIC(18,4),
|
||||
ieps_retencion NUMERIC(18,4),
|
||||
ieps_retencion_mxn NUMERIC(18,4),
|
||||
impuestos_locales_trasladado NUMERIC(18,4),
|
||||
impuestos_locales_trasladado_mxn NUMERIC(18,4),
|
||||
impuestos_locales_retenidos NUMERIC(18,4),
|
||||
impuestos_locales_retenidos_mxn NUMERIC(18,4),
|
||||
total_percepciones NUMERIC(18,4),
|
||||
total_percepciones_mxn NUMERIC(18,4),
|
||||
total_deducciones NUMERIC(18,4),
|
||||
total_deducciones_mxn NUMERIC(18,4),
|
||||
imp_retenidos_nomina NUMERIC(18,4),
|
||||
imp_retenidos_nomina_mxn NUMERIC(18,4),
|
||||
otras_deducciones_nomina NUMERIC(18,4),
|
||||
otras_deducciones_nomina_mxn NUMERIC(18,4),
|
||||
subsidio_causado NUMERIC(18,4),
|
||||
subsidio_causado_mxn NUMERIC(18,4),
|
||||
creado_en TIMESTAMP DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS conciliaciones (
|
||||
id SERIAL PRIMARY KEY,
|
||||
anio VARCHAR(4) NOT NULL,
|
||||
mes VARCHAR(2) NOT NULL,
|
||||
id_cfdi INTEGER NOT NULL UNIQUE REFERENCES cfdis(id),
|
||||
fecha_de_pago DATE NOT NULL,
|
||||
id_banco INTEGER NOT NULL REFERENCES bancos(id),
|
||||
creado_en TIMESTAMP DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS alertas (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
tipo VARCHAR(50) NOT NULL,
|
||||
titulo VARCHAR(200) NOT NULL,
|
||||
mensaje TEXT,
|
||||
prioridad VARCHAR(20) DEFAULT 'media',
|
||||
fecha_vencimiento TIMESTAMP,
|
||||
leida BOOLEAN DEFAULT FALSE,
|
||||
resuelta BOOLEAN DEFAULT FALSE,
|
||||
created_at TIMESTAMP DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS recordatorios (
|
||||
id SERIAL PRIMARY KEY,
|
||||
titulo VARCHAR(200) NOT NULL,
|
||||
descripcion TEXT,
|
||||
fecha_limite DATE NOT NULL,
|
||||
notas TEXT,
|
||||
completado BOOLEAN DEFAULT FALSE,
|
||||
privado BOOLEAN DEFAULT FALSE,
|
||||
creado_por UUID NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT NOW(),
|
||||
updated_at TIMESTAMP DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- =============================================
|
||||
-- Columns that may be missing on older tenants
|
||||
-- (CREATE TABLE IF NOT EXISTS won't add these if the table already existed)
|
||||
-- =============================================
|
||||
|
||||
ALTER TABLE cfdis ADD COLUMN IF NOT EXISTS id_conciliacion INTEGER;
|
||||
ALTER TABLE cfdis ADD COLUMN IF NOT EXISTS conciliado VARCHAR(50);
|
||||
ALTER TABLE cfdis ADD COLUMN IF NOT EXISTS source VARCHAR(20) DEFAULT 'manual';
|
||||
ALTER TABLE cfdis ADD COLUMN IF NOT EXISTS facturapi_id VARCHAR(50);
|
||||
ALTER TABLE cfdis ADD COLUMN IF NOT EXISTS regimen_fiscal_emisor VARCHAR(3);
|
||||
ALTER TABLE cfdis ADD COLUMN IF NOT EXISTS regimen_fiscal_receptor VARCHAR(3);
|
||||
ALTER TABLE cfdis ADD COLUMN IF NOT EXISTS last_sat_sync TIMESTAMP;
|
||||
ALTER TABLE cfdis ADD COLUMN IF NOT EXISTS sat_sync_job_id UUID;
|
||||
|
||||
-- Indexes
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_cfdis_fecha_emision ON cfdis(fecha_emision DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_cfdis_type ON cfdis(type);
|
||||
CREATE INDEX IF NOT EXISTS idx_cfdis_rfc_emisor ON cfdis(rfc_emisor);
|
||||
CREATE INDEX IF NOT EXISTS idx_cfdis_rfc_receptor ON cfdis(rfc_receptor);
|
||||
CREATE INDEX IF NOT EXISTS idx_cfdis_status ON cfdis(status);
|
||||
CREATE INDEX IF NOT EXISTS idx_cfdis_year_month ON cfdis(year, month);
|
||||
CREATE INDEX IF NOT EXISTS idx_cfdis_nombre_emisor_trgm ON cfdis USING gin(nombre_emisor gin_trgm_ops);
|
||||
CREATE INDEX IF NOT EXISTS idx_cfdis_nombre_receptor_trgm ON cfdis USING gin(nombre_receptor gin_trgm_ops);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_cfdis_rfc_emisor_id ON cfdis(rfc_emisor_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_cfdis_rfc_receptor_id ON cfdis(rfc_receptor_id);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_cfdi_conceptos_cfdi_id ON cfdi_conceptos(cfdi_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_cfdi_conceptos_clave ON cfdi_conceptos(clave_prod_serv);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_conciliaciones_anio_mes ON conciliaciones(anio, mes);
|
||||
CREATE INDEX IF NOT EXISTS idx_conciliaciones_id_cfdi ON conciliaciones(id_cfdi);
|
||||
CREATE INDEX IF NOT EXISTS idx_cfdis_id_conciliacion ON cfdis(id_conciliacion);
|
||||
|
||||
-- Deferred FK: cfdis.id_conciliacion -> conciliaciones(id)
|
||||
-- (cfdis is created before conciliaciones, so this constraint is added after both tables exist)
|
||||
|
||||
DO $$ BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'cfdis_id_conciliacion_fkey') THEN
|
||||
ALTER TABLE cfdis ADD CONSTRAINT cfdis_id_conciliacion_fkey FOREIGN KEY (id_conciliacion) REFERENCES conciliaciones(id);
|
||||
END IF;
|
||||
END $$;
|
||||
@@ -0,0 +1,16 @@
|
||||
-- 002_create_opiniones_cumplimiento
|
||||
-- Table for storing SAT Opinión de Cumplimiento PDFs and metadata
|
||||
|
||||
CREATE TABLE IF NOT EXISTS opiniones_cumplimiento (
|
||||
id SERIAL PRIMARY KEY,
|
||||
rfc VARCHAR(14) NOT NULL,
|
||||
razon_social VARCHAR(255),
|
||||
estatus VARCHAR(50) NOT NULL,
|
||||
folio VARCHAR(50),
|
||||
cadena_original TEXT,
|
||||
fecha_consulta TIMESTAMP NOT NULL,
|
||||
pdf BYTEA NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_opiniones_fecha ON opiniones_cumplimiento(fecha_consulta DESC);
|
||||
@@ -0,0 +1,36 @@
|
||||
-- Declaraciones provisionales del tenant: PDFs subidos por el contador con
|
||||
-- el comprobante de la declaración + opcionalmente el comprobante de pago.
|
||||
-- Al subir una declaración o un comprobante, el sistema marca como resueltas
|
||||
-- las alertas correspondientes (decl-XXX o pago-XXX) en la tabla `alertas`.
|
||||
--
|
||||
-- Reglas:
|
||||
-- - 1 declaración tipo='normal' por (año, mes) — UNIQUE parcial
|
||||
-- - N declaraciones tipo='complementaria' por (año, mes) — sin restricción
|
||||
-- - `impuestos` es un array de strings: ['IVA', 'ISR', 'IEPS', etc.] que
|
||||
-- cubre la declaración. Permite saber qué alertas resolver.
|
||||
|
||||
CREATE TABLE IF NOT EXISTS declaraciones_provisionales (
|
||||
id SERIAL PRIMARY KEY,
|
||||
año INT NOT NULL,
|
||||
mes INT NOT NULL CHECK (mes BETWEEN 1 AND 12),
|
||||
tipo VARCHAR(15) NOT NULL CHECK (tipo IN ('normal', 'complementaria')),
|
||||
impuestos TEXT[] NOT NULL, -- ['IVA', 'ISR', 'IEPS', ...]
|
||||
pdf_declaracion BYTEA NOT NULL,
|
||||
pdf_filename VARCHAR(255),
|
||||
link_pago TEXT,
|
||||
pdf_pago BYTEA,
|
||||
pdf_pago_filename VARCHAR(255),
|
||||
pagado_at TIMESTAMP,
|
||||
creado_por VARCHAR(255), -- email del user que la subió
|
||||
notas TEXT,
|
||||
created_at TIMESTAMP DEFAULT NOW(),
|
||||
updated_at TIMESTAMP DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_declaraciones_periodo ON declaraciones_provisionales(año DESC, mes DESC);
|
||||
|
||||
-- Solo 1 declaración tipo='normal' por (año, mes). Las complementarias no
|
||||
-- tienen restricción de cantidad.
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS uniq_declaracion_normal_mes
|
||||
ON declaraciones_provisionales(año, mes)
|
||||
WHERE tipo = 'normal';
|
||||
@@ -0,0 +1,11 @@
|
||||
-- La "liga de pago" de la declaración es un PDF (no un URL). Reemplazamos
|
||||
-- la columna TEXT por un par BYTEA+filename, consistente con pdf_declaracion
|
||||
-- y pdf_pago. Si la migración 003 aún no se desplegó en algún ambiente,
|
||||
-- este ALTER aplica igual (DROP IF EXISTS + ADD COLUMN IF NOT EXISTS).
|
||||
|
||||
ALTER TABLE declaraciones_provisionales
|
||||
DROP COLUMN IF EXISTS link_pago;
|
||||
|
||||
ALTER TABLE declaraciones_provisionales
|
||||
ADD COLUMN IF NOT EXISTS pdf_liga_pago BYTEA,
|
||||
ADD COLUMN IF NOT EXISTS pdf_liga_pago_filename VARCHAR(255);
|
||||
@@ -0,0 +1,24 @@
|
||||
-- Constancia de Situación Fiscal: PDF descargado del portal SAT con Playwright
|
||||
-- + FIEL. Se descarga automáticamente el 1° de cada mes y al primer upload de
|
||||
-- FIEL del tenant. Retención 5 años (similar a declaraciones_provisionales).
|
||||
--
|
||||
-- `datos` es un JSONB con el shape `ConstanciaSituacionFiscal` del prototipo
|
||||
-- (domicilio, régimenes activos, actividades, obligaciones, sellos). Se
|
||||
-- guarda completo para poder re-hidratar la UI sin re-parsear el PDF, y
|
||||
-- comparar entre consultas (detectar cambios de domicilio/régimen).
|
||||
|
||||
CREATE TABLE IF NOT EXISTS constancias_situacion_fiscal (
|
||||
id SERIAL PRIMARY KEY,
|
||||
rfc VARCHAR(13) NOT NULL,
|
||||
id_cif VARCHAR(20),
|
||||
razon_social TEXT,
|
||||
estatus_padron VARCHAR(30),
|
||||
fecha_emision TEXT, -- "GUADALAJARA, JALISCO A 14 DE ABRIL DE 2026" (formato libre del SAT)
|
||||
datos JSONB NOT NULL, -- shape ConstanciaSituacionFiscal completo
|
||||
pdf BYTEA NOT NULL,
|
||||
fecha_consulta TIMESTAMP DEFAULT NOW(),
|
||||
created_at TIMESTAMP DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_csf_fecha_consulta
|
||||
ON constancias_situacion_fiscal(fecha_consulta DESC);
|
||||
@@ -0,0 +1,17 @@
|
||||
CREATE TABLE IF NOT EXISTS tenant_migrations (
|
||||
scope varchar(50) NOT NULL,
|
||||
version int NOT NULL,
|
||||
name varchar(255),
|
||||
applied_at timestamptz DEFAULT now(),
|
||||
PRIMARY KEY (scope, version)
|
||||
);
|
||||
|
||||
INSERT INTO tenant_migrations (scope, version, name)
|
||||
VALUES
|
||||
('legacy', 1, '001_initial_schema'),
|
||||
('legacy', 2, '002_create_opiniones_cumplimiento'),
|
||||
('legacy', 3, '003_create_declaraciones_provisionales'),
|
||||
('legacy', 4, '004_declaraciones_liga_pago_pdf'),
|
||||
('legacy', 5, '005_create_constancias_situacion_fiscal'),
|
||||
('legacy', 6, '006_tenant_migrations_tracking')
|
||||
ON CONFLICT (scope, version) DO NOTHING;
|
||||
18
apps/api/src/migrations/tenant/007_entidades_gestionadas.sql
Normal file
18
apps/api/src/migrations/tenant/007_entidades_gestionadas.sql
Normal file
@@ -0,0 +1,18 @@
|
||||
CREATE TABLE IF NOT EXISTS entidades_gestionadas (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
tipo varchar(20) NOT NULL,
|
||||
nombre text NOT NULL,
|
||||
identificador text,
|
||||
supervisor_user_id uuid,
|
||||
active boolean DEFAULT true,
|
||||
created_at timestamptz DEFAULT now(),
|
||||
updated_at timestamptz DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS ix_entidades_supervisor ON entidades_gestionadas(supervisor_user_id);
|
||||
CREATE INDEX IF NOT EXISTS ix_entidades_tipo ON entidades_gestionadas(tipo, active);
|
||||
CREATE INDEX IF NOT EXISTS ix_entidades_identificador ON entidades_gestionadas(identificador);
|
||||
|
||||
INSERT INTO tenant_migrations (scope, version, name)
|
||||
VALUES ('core', 7, '007_entidades_gestionadas')
|
||||
ON CONFLICT (scope, version) DO NOTHING;
|
||||
27
apps/api/src/migrations/tenant/008_carteras.sql
Normal file
27
apps/api/src/migrations/tenant/008_carteras.sql
Normal file
@@ -0,0 +1,27 @@
|
||||
CREATE TABLE IF NOT EXISTS carteras (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
supervisor_user_id uuid NOT NULL,
|
||||
nombre text NOT NULL,
|
||||
descripcion text,
|
||||
created_at timestamptz DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS ix_carteras_supervisor ON carteras(supervisor_user_id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS cartera_entidades (
|
||||
cartera_id uuid NOT NULL REFERENCES carteras(id) ON DELETE CASCADE,
|
||||
entidad_id uuid NOT NULL REFERENCES entidades_gestionadas(id) ON DELETE CASCADE,
|
||||
added_at timestamptz DEFAULT now(),
|
||||
PRIMARY KEY (cartera_id, entidad_id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS cartera_auxiliares (
|
||||
cartera_id uuid NOT NULL REFERENCES carteras(id) ON DELETE CASCADE,
|
||||
auxiliar_user_id uuid NOT NULL,
|
||||
added_at timestamptz DEFAULT now(),
|
||||
PRIMARY KEY (cartera_id, auxiliar_user_id)
|
||||
);
|
||||
|
||||
INSERT INTO tenant_migrations (scope, version, name)
|
||||
VALUES ('core', 8, '008_carteras')
|
||||
ON CONFLICT (scope, version) DO NOTHING;
|
||||
10
apps/api/src/migrations/tenant/009_cliente_accesos.sql
Normal file
10
apps/api/src/migrations/tenant/009_cliente_accesos.sql
Normal file
@@ -0,0 +1,10 @@
|
||||
CREATE TABLE IF NOT EXISTS cliente_accesos (
|
||||
user_id uuid NOT NULL,
|
||||
entidad_id uuid NOT NULL REFERENCES entidades_gestionadas(id) ON DELETE CASCADE,
|
||||
granted_at timestamptz DEFAULT now(),
|
||||
PRIMARY KEY (user_id, entidad_id)
|
||||
);
|
||||
|
||||
INSERT INTO tenant_migrations (scope, version, name)
|
||||
VALUES ('core', 9, '009_cliente_accesos')
|
||||
ON CONFLICT (scope, version) DO NOTHING;
|
||||
13
apps/api/src/migrations/tenant/010_contribuyentes.sql
Normal file
13
apps/api/src/migrations/tenant/010_contribuyentes.sql
Normal file
@@ -0,0 +1,13 @@
|
||||
CREATE TABLE IF NOT EXISTS contribuyentes (
|
||||
entidad_id uuid PRIMARY KEY REFERENCES entidades_gestionadas(id) ON DELETE CASCADE,
|
||||
rfc varchar(13) NOT NULL UNIQUE,
|
||||
regimen_fiscal varchar(3),
|
||||
codigo_postal varchar(5),
|
||||
domicilio jsonb
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS ix_contribuyentes_rfc ON contribuyentes(rfc);
|
||||
|
||||
INSERT INTO tenant_migrations (scope, version, name)
|
||||
VALUES ('vertical-contable', 10, '010_contribuyentes')
|
||||
ON CONFLICT (scope, version) DO NOTHING;
|
||||
@@ -0,0 +1,23 @@
|
||||
CREATE TABLE IF NOT EXISTS fiel_contribuyente (
|
||||
contribuyente_id uuid PRIMARY KEY REFERENCES contribuyentes(entidad_id) ON DELETE CASCADE,
|
||||
rfc varchar(13) NOT NULL,
|
||||
cer_data bytea NOT NULL,
|
||||
key_data bytea NOT NULL,
|
||||
key_password_enc bytea NOT NULL,
|
||||
cer_iv bytea NOT NULL,
|
||||
cer_tag bytea NOT NULL,
|
||||
key_iv bytea NOT NULL,
|
||||
key_tag bytea NOT NULL,
|
||||
password_iv bytea NOT NULL,
|
||||
password_tag bytea NOT NULL,
|
||||
serial_number varchar(50),
|
||||
valid_from timestamptz NOT NULL,
|
||||
valid_until timestamptz NOT NULL,
|
||||
is_active boolean DEFAULT true,
|
||||
uploaded_at timestamptz DEFAULT now(),
|
||||
updated_at timestamptz DEFAULT now()
|
||||
);
|
||||
|
||||
INSERT INTO tenant_migrations (scope, version, name)
|
||||
VALUES ('vertical-contable', 11, '011_fiel_per_contribuyente')
|
||||
ON CONFLICT (scope, version) DO NOTHING;
|
||||
@@ -0,0 +1,11 @@
|
||||
CREATE TABLE IF NOT EXISTS facturapi_orgs (
|
||||
contribuyente_id uuid PRIMARY KEY REFERENCES contribuyentes(entidad_id) ON DELETE CASCADE,
|
||||
facturapi_org_id text NOT NULL UNIQUE,
|
||||
csd_uploaded boolean DEFAULT false,
|
||||
active boolean DEFAULT true,
|
||||
created_at timestamptz DEFAULT now()
|
||||
);
|
||||
|
||||
INSERT INTO tenant_migrations (scope, version, name)
|
||||
VALUES ('vertical-contable', 12, '012_facturapi_per_contribuyente')
|
||||
ON CONFLICT (scope, version) DO NOTHING;
|
||||
@@ -0,0 +1,7 @@
|
||||
ALTER TABLE cfdis ADD COLUMN IF NOT EXISTS contribuyente_id uuid REFERENCES contribuyentes(entidad_id);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS ix_cfdi_contribuyente ON cfdis(contribuyente_id) WHERE contribuyente_id IS NOT NULL;
|
||||
|
||||
INSERT INTO tenant_migrations (scope, version, name)
|
||||
VALUES ('vertical-contable', 13, '013_cfdi_contribuyente_id')
|
||||
ON CONFLICT (scope, version) DO NOTHING;
|
||||
52
apps/api/src/migrations/tenant/014_metricas_mensuales.sql
Normal file
52
apps/api/src/migrations/tenant/014_metricas_mensuales.sql
Normal file
@@ -0,0 +1,52 @@
|
||||
CREATE TABLE IF NOT EXISTS metricas_mensuales (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
contribuyente_id uuid NOT NULL REFERENCES contribuyentes(entidad_id) ON DELETE CASCADE,
|
||||
anio smallint NOT NULL,
|
||||
mes smallint NOT NULL,
|
||||
regimen_fiscal varchar(3),
|
||||
formula_version smallint DEFAULT 1,
|
||||
iva_trasladado_16 numeric(18,2) DEFAULT 0,
|
||||
iva_trasladado_8 numeric(18,2) DEFAULT 0,
|
||||
iva_trasladado_0 numeric(18,2) DEFAULT 0,
|
||||
iva_trasladado_exento numeric(18,2) DEFAULT 0,
|
||||
iva_trasladado_total numeric(18,2) DEFAULT 0,
|
||||
iva_acreditable numeric(18,2) DEFAULT 0,
|
||||
iva_retenido_cobrado numeric(18,2) DEFAULT 0,
|
||||
iva_retenido_pagado numeric(18,2) DEFAULT 0,
|
||||
iva_resultado numeric(18,2) DEFAULT 0,
|
||||
iva_a_favor_mes numeric(18,2) DEFAULT 0,
|
||||
isr_ingresos_brutos numeric(18,2) DEFAULT 0,
|
||||
isr_deducciones_autoriz numeric(18,2) DEFAULT 0,
|
||||
isr_base numeric(18,2) DEFAULT 0,
|
||||
isr_causado numeric(18,2) DEFAULT 0,
|
||||
isr_retenido numeric(18,2) DEFAULT 0,
|
||||
isr_a_pagar numeric(18,2) DEFAULT 0,
|
||||
ieps_trasladado numeric(18,2) DEFAULT 0,
|
||||
ieps_acreditable numeric(18,2) DEFAULT 0,
|
||||
cfdis_emitidos_count int DEFAULT 0,
|
||||
cfdis_recibidos_count int DEFAULT 0,
|
||||
cfdis_cancelados_count int DEFAULT 0,
|
||||
ingresos_devengados numeric(18,2) DEFAULT 0,
|
||||
ingresos_cobrados numeric(18,2) DEFAULT 0,
|
||||
egresos_devengados numeric(18,2) DEFAULT 0,
|
||||
egresos_pagados numeric(18,2) DEFAULT 0,
|
||||
utilidad_devengada numeric(18,2) DEFAULT 0,
|
||||
utilidad_realizada numeric(18,2) DEFAULT 0,
|
||||
flujo_entradas numeric(18,2) DEFAULT 0,
|
||||
flujo_salidas numeric(18,2) DEFAULT 0,
|
||||
flujo_neto numeric(18,2) DEFAULT 0,
|
||||
cxc_saldo_final numeric(18,2) DEFAULT 0,
|
||||
cxp_saldo_final numeric(18,2) DEFAULT 0,
|
||||
cxc_cfdis_count int DEFAULT 0,
|
||||
cxp_cfdis_count int DEFAULT 0,
|
||||
cerrado boolean DEFAULT false,
|
||||
computed_at timestamptz DEFAULT now(),
|
||||
source_max_cfdi_at timestamptz,
|
||||
UNIQUE (contribuyente_id, anio, mes, regimen_fiscal)
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS ix_metricas_contrib_anio ON metricas_mensuales(contribuyente_id, anio DESC, mes DESC);
|
||||
CREATE INDEX IF NOT EXISTS ix_metricas_cerrado ON metricas_mensuales(cerrado, computed_at);
|
||||
|
||||
INSERT INTO tenant_migrations (scope, version, name)
|
||||
VALUES ('vertical-contable', 14, '014_metricas_mensuales')
|
||||
ON CONFLICT (scope, version) DO NOTHING;
|
||||
@@ -0,0 +1,24 @@
|
||||
CREATE TABLE IF NOT EXISTS metricas_acumuladas_anuales (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
contribuyente_id uuid NOT NULL REFERENCES contribuyentes(entidad_id) ON DELETE CASCADE,
|
||||
anio smallint NOT NULL,
|
||||
regimen_fiscal varchar(3),
|
||||
formula_version smallint DEFAULT 1,
|
||||
iva_a_favor_arrastrado numeric(18,2) DEFAULT 0,
|
||||
iva_a_favor_generado numeric(18,2) DEFAULT 0,
|
||||
iva_a_favor_aplicado numeric(18,2) DEFAULT 0,
|
||||
iva_a_favor_saldo numeric(18,2) DEFAULT 0,
|
||||
ingresos_anuales numeric(18,2) DEFAULT 0,
|
||||
deducciones_anuales numeric(18,2) DEFAULT 0,
|
||||
utilidad_anual numeric(18,2) DEFAULT 0,
|
||||
isr_causado_anual numeric(18,2) DEFAULT 0,
|
||||
isr_retenido_anual numeric(18,2) DEFAULT 0,
|
||||
isr_a_pagar_anual numeric(18,2) DEFAULT 0,
|
||||
cerrado boolean DEFAULT false,
|
||||
computed_at timestamptz DEFAULT now(),
|
||||
UNIQUE (contribuyente_id, anio, regimen_fiscal)
|
||||
);
|
||||
|
||||
INSERT INTO tenant_migrations (scope, version, name)
|
||||
VALUES ('vertical-contable', 15, '015_metricas_acumuladas_anuales')
|
||||
ON CONFLICT (scope, version) DO NOTHING;
|
||||
@@ -0,0 +1,19 @@
|
||||
CREATE TABLE IF NOT EXISTS metricas_por_contraparte_anuales (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
contribuyente_id uuid NOT NULL REFERENCES contribuyentes(entidad_id) ON DELETE CASCADE,
|
||||
anio smallint NOT NULL,
|
||||
rfc_contraparte varchar(13) NOT NULL,
|
||||
nombre_contraparte text,
|
||||
tipo char(1),
|
||||
subtotal numeric(18,2),
|
||||
total numeric(18,2),
|
||||
cfdis_count int,
|
||||
concentracion_pct numeric(5,2),
|
||||
computed_at timestamptz DEFAULT now(),
|
||||
UNIQUE (contribuyente_id, anio, rfc_contraparte, tipo)
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS ix_metricas_contraparte_top ON metricas_por_contraparte_anuales(contribuyente_id, anio, tipo, total DESC);
|
||||
|
||||
INSERT INTO tenant_migrations (scope, version, name)
|
||||
VALUES ('vertical-contable', 16, '016_metricas_por_contraparte_anuales')
|
||||
ON CONFLICT (scope, version) DO NOTHING;
|
||||
@@ -0,0 +1,12 @@
|
||||
CREATE TABLE IF NOT EXISTS metricas_invalidaciones (
|
||||
contribuyente_id uuid NOT NULL,
|
||||
anio smallint NOT NULL,
|
||||
mes smallint NOT NULL,
|
||||
reason text,
|
||||
marcado_at timestamptz DEFAULT now(),
|
||||
PRIMARY KEY (contribuyente_id, anio, mes)
|
||||
);
|
||||
|
||||
INSERT INTO tenant_migrations (scope, version, name)
|
||||
VALUES ('vertical-contable', 17, '017_metricas_invalidaciones')
|
||||
ON CONFLICT (scope, version) DO NOTHING;
|
||||
@@ -0,0 +1,20 @@
|
||||
CREATE TABLE IF NOT EXISTS obligaciones_contribuyente (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
contribuyente_id uuid NOT NULL REFERENCES contribuyentes(entidad_id) ON DELETE CASCADE,
|
||||
catalogo_id text,
|
||||
nombre text NOT NULL,
|
||||
fundamento text,
|
||||
frecuencia text,
|
||||
fecha_limite text,
|
||||
categoria text,
|
||||
activa boolean DEFAULT true,
|
||||
es_recomendada boolean DEFAULT false,
|
||||
es_custom boolean DEFAULT false,
|
||||
created_at timestamptz DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS ix_obligaciones_contrib ON obligaciones_contribuyente(contribuyente_id, activa);
|
||||
|
||||
INSERT INTO tenant_migrations (scope, version, name)
|
||||
VALUES ('vertical-contable', 18, '018_obligaciones_contribuyente')
|
||||
ON CONFLICT (scope, version) DO NOTHING;
|
||||
@@ -0,0 +1,8 @@
|
||||
ALTER TABLE obligaciones_contribuyente ADD COLUMN IF NOT EXISTS completada boolean DEFAULT false;
|
||||
ALTER TABLE obligaciones_contribuyente ADD COLUMN IF NOT EXISTS completada_at timestamptz;
|
||||
ALTER TABLE obligaciones_contribuyente ADD COLUMN IF NOT EXISTS completada_por uuid;
|
||||
ALTER TABLE obligaciones_contribuyente ADD COLUMN IF NOT EXISTS periodo_completado varchar(7); -- "2026-04" (year-month)
|
||||
|
||||
INSERT INTO tenant_migrations (scope, version, name)
|
||||
VALUES ('vertical-contable', 19, '019_obligaciones_completada')
|
||||
ON CONFLICT (scope, version) DO NOTHING;
|
||||
17
apps/api/src/migrations/tenant/020_obligacion_periodos.sql
Normal file
17
apps/api/src/migrations/tenant/020_obligacion_periodos.sql
Normal file
@@ -0,0 +1,17 @@
|
||||
CREATE TABLE IF NOT EXISTS obligacion_periodos (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
obligacion_id uuid NOT NULL REFERENCES obligaciones_contribuyente(id) ON DELETE CASCADE,
|
||||
periodo varchar(7) NOT NULL,
|
||||
completada boolean DEFAULT false,
|
||||
completada_at timestamptz,
|
||||
completada_por uuid,
|
||||
notas text,
|
||||
created_at timestamptz DEFAULT now(),
|
||||
UNIQUE (obligacion_id, periodo)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS ix_obligacion_periodos_periodo ON obligacion_periodos(periodo);
|
||||
|
||||
INSERT INTO tenant_migrations (scope, version, name)
|
||||
VALUES ('vertical-contable', 20, '020_obligacion_periodos')
|
||||
ON CONFLICT (scope, version) DO NOTHING;
|
||||
@@ -0,0 +1,15 @@
|
||||
-- Add periodicidad (period type) and monto_pago (payment amount) to declaraciones.
|
||||
-- periodicidad replaces the assumption that all declarations are monthly.
|
||||
-- monto_pago = 0 means the declaration results in $0 to pay (auto-mark as paid).
|
||||
|
||||
ALTER TABLE declaraciones_provisionales
|
||||
ADD COLUMN IF NOT EXISTS periodicidad VARCHAR(15) NOT NULL DEFAULT 'mensual'
|
||||
CHECK (periodicidad IN ('mensual', 'bimestral', 'trimestral', 'semestral', 'anual'));
|
||||
|
||||
ALTER TABLE declaraciones_provisionales
|
||||
ADD COLUMN IF NOT EXISTS monto_pago NUMERIC(15,2);
|
||||
|
||||
-- For existing rows that already have a payment proof, backfill pagado_at if null
|
||||
UPDATE declaraciones_provisionales
|
||||
SET pagado_at = updated_at
|
||||
WHERE pdf_pago IS NOT NULL AND pagado_at IS NULL;
|
||||
23
apps/api/src/migrations/tenant/022_carteras_subcarteras.sql
Normal file
23
apps/api/src/migrations/tenant/022_carteras_subcarteras.sql
Normal file
@@ -0,0 +1,23 @@
|
||||
-- Subcarteras: a cartera can be a child of another cartera.
|
||||
-- Top-level carteras belong to a supervisor (or owner).
|
||||
-- Subcarteras belong to an auxiliar within a parent cartera.
|
||||
|
||||
ALTER TABLE carteras
|
||||
ADD COLUMN IF NOT EXISTS parent_id uuid REFERENCES carteras(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE carteras
|
||||
ADD COLUMN IF NOT EXISTS auxiliar_user_id uuid;
|
||||
|
||||
-- Allow supervisor_user_id to be NULL for subcarteras (inherited from parent)
|
||||
ALTER TABLE carteras
|
||||
ALTER COLUMN supervisor_user_id DROP NOT NULL;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS ix_carteras_parent ON carteras(parent_id);
|
||||
CREATE INDEX IF NOT EXISTS ix_carteras_auxiliar ON carteras(auxiliar_user_id);
|
||||
|
||||
-- Track which supervisor an auxiliar reports to (1:1 per auxiliar)
|
||||
CREATE TABLE IF NOT EXISTS auxiliar_supervisores (
|
||||
auxiliar_user_id uuid NOT NULL PRIMARY KEY,
|
||||
supervisor_user_id uuid NOT NULL,
|
||||
created_at timestamptz DEFAULT now()
|
||||
);
|
||||
@@ -0,0 +1,4 @@
|
||||
-- Bancos belong to individual contribuyentes, not the whole tenant.
|
||||
-- Used for conciliación per-contribuyente.
|
||||
ALTER TABLE bancos ADD COLUMN IF NOT EXISTS contribuyente_id uuid;
|
||||
CREATE INDEX IF NOT EXISTS ix_bancos_contribuyente ON bancos(contribuyente_id);
|
||||
12
apps/api/src/migrations/tenant/024_cfdi_descartados.sql
Normal file
12
apps/api/src/migrations/tenant/024_cfdi_descartados.sql
Normal file
@@ -0,0 +1,12 @@
|
||||
-- CFDIs descartados de alertas (ej: discrepancias de régimen que el usuario revisó y decidió ignorar).
|
||||
-- El descarte se aplica por tipo de alerta y cfdi_id.
|
||||
CREATE TABLE IF NOT EXISTS cfdi_descartados (
|
||||
id serial PRIMARY KEY,
|
||||
cfdi_id integer NOT NULL,
|
||||
tipo_alerta text NOT NULL, -- e.g. 'discrepancia-regimen'
|
||||
descartado_por text, -- email or userId
|
||||
created_at timestamptz DEFAULT now(),
|
||||
UNIQUE (cfdi_id, tipo_alerta)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS ix_cfdi_descartados_tipo ON cfdi_descartados(tipo_alerta);
|
||||
@@ -0,0 +1,18 @@
|
||||
-- Amplía contribuyentes.regimen_fiscal a TEXT para soportar CSV de múltiples
|
||||
-- regímenes (ej. "626,605"). La migración 010 original lo declaró varchar(3)
|
||||
-- asumiendo un solo régimen por contribuyente, pero el código sincroniza CSV
|
||||
-- desde la CSF (sincronizarDatosFiscales en constancia.service.ts).
|
||||
--
|
||||
-- Síntoma antes del fix: el sync falla con "el valor es demasiado largo para
|
||||
-- el tipo character varying(3)" cuando un contribuyente tiene ≥2 regímenes
|
||||
-- activos en su CSF, y los campos regimen_fiscal/codigo_postal/domicilio
|
||||
-- quedan NULL.
|
||||
--
|
||||
-- Idempotente: si ya es text (patito tenía parche manual), el ALTER es no-op
|
||||
-- en términos de filas — Postgres lo resuelve como metadata change.
|
||||
|
||||
ALTER TABLE contribuyentes ALTER COLUMN regimen_fiscal TYPE text;
|
||||
|
||||
INSERT INTO tenant_migrations (scope, version, name)
|
||||
VALUES ('vertical-contable', 25, '025_contribuyentes_regimen_fiscal_text')
|
||||
ON CONFLICT (scope, version) DO NOTHING;
|
||||
@@ -0,0 +1,47 @@
|
||||
-- Normaliza el case de cfdis.uuid y elimina duplicados generados por el
|
||||
-- mismatch case-sensitive entre los dos paths de sync SAT:
|
||||
-- - source='sat' (XML parser): insertaba UUIDs como venían del XML (lowercase)
|
||||
-- - source='sat-metadata' (CSV parser): insertaba UUIDs del CSV (UPPERCASE)
|
||||
-- La constraint UNIQUE(uuid) de Postgres es case-sensitive → ambos convivían
|
||||
-- como filas distintas, duplicando el CFDI en todas las métricas.
|
||||
--
|
||||
-- Estrategia (idempotente — en tenants sin duplicados es no-op):
|
||||
-- 1. Propagar status=Cancelado de metadata→sat si corresponde (guard de
|
||||
-- data loss; verificado que en Patito no aplica a ninguna fila, pero la
|
||||
-- cláusula queda como protección para futuros despachos).
|
||||
-- 2. Borrar las filas 'sat-metadata' que tengan par 'sat' con el mismo UUID
|
||||
-- (case-insensitive). Las filas sat-metadata sin par se conservan (son
|
||||
-- legítimas: CFDIs cancelados sin XML).
|
||||
-- 3. Normalizar todos los UUIDs restantes a lowercase.
|
||||
-- El código de saveCfdis/saveMetadata también fue actualizado para (a) matchear
|
||||
-- case-insensitive, (b) insertar siempre en lowercase.
|
||||
|
||||
-- 1. Propagar Cancelado antes de borrar
|
||||
UPDATE cfdis sat
|
||||
SET status = 'Cancelado',
|
||||
fecha_cancelacion = COALESCE(sat.fecha_cancelacion, meta.fecha_cancelacion),
|
||||
actualizado_en = NOW()
|
||||
FROM cfdis meta
|
||||
WHERE LOWER(sat.uuid) = LOWER(meta.uuid)
|
||||
AND sat.id != meta.id
|
||||
AND sat.source = 'sat'
|
||||
AND meta.source = 'sat-metadata'
|
||||
AND meta.status = 'Cancelado'
|
||||
AND sat.status != 'Cancelado';
|
||||
|
||||
-- 2. Borrar duplicados sat-metadata
|
||||
DELETE FROM cfdis meta
|
||||
WHERE meta.source = 'sat-metadata'
|
||||
AND EXISTS (
|
||||
SELECT 1 FROM cfdis sat
|
||||
WHERE LOWER(meta.uuid) = LOWER(sat.uuid)
|
||||
AND sat.id != meta.id
|
||||
AND sat.source = 'sat'
|
||||
);
|
||||
|
||||
-- 3. Normalizar a lowercase
|
||||
UPDATE cfdis SET uuid = LOWER(uuid) WHERE uuid IS NOT NULL AND uuid != LOWER(uuid);
|
||||
|
||||
INSERT INTO tenant_migrations (scope, version, name)
|
||||
VALUES ('vertical-contable', 26, '026_normalize_cfdi_uuid_case')
|
||||
ON CONFLICT (scope, version) DO NOTHING;
|
||||
@@ -0,0 +1,22 @@
|
||||
-- Reemplaza el UNIQUE (uuid) case-sensitive por un índice funcional
|
||||
-- UNIQUE (LOWER(uuid)), como defensa en profundidad contra duplicados por
|
||||
-- mismatch de case. El código fuente ya normaliza a lowercase en el insert
|
||||
-- (saveCfdis y saveMetadata en sat.service.ts), pero este constraint previene
|
||||
-- que cualquier insert manual o vía futuro path pueda reintroducir el bug.
|
||||
--
|
||||
-- Prerequisito: migración 026 ya normalizó todos los UUIDs existentes a
|
||||
-- lowercase y eliminó duplicados case-insensitive. Si no se aplicó antes, el
|
||||
-- CREATE UNIQUE INDEX fallará con "could not create unique index" y habrá que
|
||||
-- correr 026 primero.
|
||||
--
|
||||
-- Idempotente: si el índice nuevo ya existe, IF NOT EXISTS lo salta.
|
||||
|
||||
ALTER TABLE cfdis DROP CONSTRAINT IF EXISTS cfdis_uuid_key;
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS cfdis_uuid_lower_key
|
||||
ON cfdis (LOWER(uuid))
|
||||
WHERE uuid IS NOT NULL;
|
||||
|
||||
INSERT INTO tenant_migrations (scope, version, name)
|
||||
VALUES ('vertical-contable', 27, '027_cfdi_uuid_unique_case_insensitive')
|
||||
ON CONFLICT (scope, version) DO NOTHING;
|
||||
26
apps/api/src/migrations/tenant/028_documentos_extras.sql
Normal file
26
apps/api/src/migrations/tenant/028_documentos_extras.sql
Normal file
@@ -0,0 +1,26 @@
|
||||
-- Pestaña "Extras" en /documentos: PDFs libres (acuses SAT, contratos, poderes,
|
||||
-- estados de cuenta, comprobantes) organizados por contribuyente con categoría
|
||||
-- de texto libre.
|
||||
CREATE TABLE IF NOT EXISTS documentos_extras (
|
||||
id serial PRIMARY KEY,
|
||||
contribuyente_id uuid REFERENCES contribuyentes(entidad_id) ON DELETE CASCADE,
|
||||
nombre varchar(255) NOT NULL,
|
||||
descripcion text,
|
||||
categoria varchar(100),
|
||||
pdf bytea NOT NULL,
|
||||
pdf_filename varchar(255) NOT NULL,
|
||||
subido_por varchar(255),
|
||||
created_at timestamptz DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS ix_documentos_extras_contrib
|
||||
ON documentos_extras(contribuyente_id, created_at DESC)
|
||||
WHERE contribuyente_id IS NOT NULL;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS ix_documentos_extras_categoria
|
||||
ON documentos_extras(categoria)
|
||||
WHERE categoria IS NOT NULL;
|
||||
|
||||
INSERT INTO tenant_migrations (scope, version, name)
|
||||
VALUES ('vertical-contable', 28, '028_documentos_extras')
|
||||
ON CONFLICT (scope, version) DO NOTHING;
|
||||
@@ -0,0 +1,17 @@
|
||||
-- #6 Trazabilidad declaración↔obligación: agrega FK a declaraciones_provisionales
|
||||
-- en obligacion_periodos. ON DELETE SET NULL porque si la declaración se borra
|
||||
-- el periodo puede seguir completado (el usuario puede haberlo cerrado sin
|
||||
-- re-subir, o la completitud viene de otra fuente — "marcar manualmente"
|
||||
-- via UI, etc.). La UI puede mostrar "via Declaración #123" cuando hay FK.
|
||||
|
||||
ALTER TABLE obligacion_periodos
|
||||
ADD COLUMN IF NOT EXISTS declaracion_id integer
|
||||
REFERENCES declaraciones_provisionales(id) ON DELETE SET NULL;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS ix_obligacion_periodos_declaracion
|
||||
ON obligacion_periodos(declaracion_id)
|
||||
WHERE declaracion_id IS NOT NULL;
|
||||
|
||||
INSERT INTO tenant_migrations (scope, version, name)
|
||||
VALUES ('vertical-contable', 30, '030_obligacion_periodos_declaracion_id')
|
||||
ON CONFLICT (scope, version) DO NOTHING;
|
||||
@@ -0,0 +1,27 @@
|
||||
-- Fix: las declaraciones provisionales no distinguían contribuyente. En un
|
||||
-- despacho con N RFCs, la declaración IVA de Alexa aparecía también cuando
|
||||
-- se seleccionaba a Carlos. Agregamos FK nullable para linkear, y las
|
||||
-- existentes quedan con NULL (interpretadas como "tenant-wide / legacy").
|
||||
-- `ON DELETE SET NULL` para que borrar un contribuyente no tire declaraciones.
|
||||
|
||||
ALTER TABLE declaraciones_provisionales
|
||||
ADD COLUMN IF NOT EXISTS contribuyente_id uuid
|
||||
REFERENCES contribuyentes(entidad_id) ON DELETE SET NULL;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS ix_declaraciones_contribuyente
|
||||
ON declaraciones_provisionales(contribuyente_id, año DESC, mes DESC)
|
||||
WHERE contribuyente_id IS NOT NULL;
|
||||
|
||||
-- Reemplaza el UNIQUE (año, mes) WHERE tipo='normal' por uno que incluye
|
||||
-- contribuyente: cada RFC debe poder tener su propia declaración normal
|
||||
-- para el mismo mes. Postgres trata NULL != NULL en índices, así que
|
||||
-- declaraciones legacy sin contribuyente siguen pudiendo coexistir entre
|
||||
-- sí — no se auto-de-duplican, pero tampoco bloquean las nuevas.
|
||||
DROP INDEX IF EXISTS uniq_declaracion_normal_mes;
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS uniq_declaracion_normal_mes_contrib
|
||||
ON declaraciones_provisionales(año, mes, contribuyente_id)
|
||||
WHERE tipo = 'normal';
|
||||
|
||||
INSERT INTO tenant_migrations (scope, version, name)
|
||||
VALUES ('vertical-contable', 31, '031_declaraciones_contribuyente_id')
|
||||
ON CONFLICT (scope, version) DO NOTHING;
|
||||
22
apps/api/src/migrations/tenant/032_cfdis_relaciones.sql
Normal file
22
apps/api/src/migrations/tenant/032_cfdis_relaciones.sql
Normal file
@@ -0,0 +1,22 @@
|
||||
-- Agrega soporte para CfdiRelacionados del propio comprobante (CFDI 4.0).
|
||||
-- El campo existente `uuid_relacionado` se sigue usando para DoctoRelacionado
|
||||
-- del complemento de Pagos (tipo P). Estas dos columnas nuevas son para los
|
||||
-- CfdiRelacionados a nivel raíz del comprobante (típico en tipo E — notas
|
||||
-- de crédito relacionadas a facturas I, P, o a anticipos aplicados).
|
||||
--
|
||||
-- `cfdi_tipo_relacion` — clave SAT de 2 chars (01 NC, 02 Sustitución,
|
||||
-- 03 Devolución, 04 Sustitución CFDIs previos, 05 Traslados mercancía,
|
||||
-- 06 Factura por traslado previo, 07 Aplicación de anticipo).
|
||||
-- `cfdis_relacionados` — UUIDs pipe-separated del/los CfdiRelacionado.
|
||||
|
||||
ALTER TABLE cfdis
|
||||
ADD COLUMN IF NOT EXISTS cfdi_tipo_relacion VARCHAR(2),
|
||||
ADD COLUMN IF NOT EXISTS cfdis_relacionados TEXT;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS ix_cfdis_tipo_relacion
|
||||
ON cfdis(cfdi_tipo_relacion)
|
||||
WHERE cfdi_tipo_relacion IS NOT NULL;
|
||||
|
||||
INSERT INTO tenant_migrations (scope, version, name)
|
||||
VALUES ('vertical-contable', 32, '032_cfdis_relaciones')
|
||||
ON CONFLICT (scope, version) DO NOTHING;
|
||||
@@ -0,0 +1,11 @@
|
||||
-- Marca el timestamp del último rechazo SAT que sugiere que el CSD
|
||||
-- aún no está propagado en la Lista de Contribuyentes Obligados (LCO).
|
||||
-- La propagación tarda 24-72h; el frontend muestra un banner mientras
|
||||
-- esta marca esté dentro de las últimas 24h.
|
||||
|
||||
ALTER TABLE facturapi_orgs
|
||||
ADD COLUMN IF NOT EXISTS last_lco_rejection_at timestamptz;
|
||||
|
||||
INSERT INTO tenant_migrations (scope, version, name)
|
||||
VALUES ('vertical-contable', 33, '033_facturapi_orgs_lco_rejection')
|
||||
ON CONFLICT (scope, version) DO NOTHING;
|
||||
@@ -0,0 +1,12 @@
|
||||
-- Preferencias de notificación por correo, por contribuyente.
|
||||
-- Default: objeto vacío → el código interpreta "todo activado" como
|
||||
-- comportamiento previo. Cuando el user desactiva un tipo, se guarda
|
||||
-- `{ "<tipo>": false }`. Tipos soportados (informativos):
|
||||
-- weekly_update, fiel_notification, documento_subido, subscription_expiring
|
||||
|
||||
ALTER TABLE contribuyentes
|
||||
ADD COLUMN IF NOT EXISTS email_preferences jsonb DEFAULT '{}'::jsonb;
|
||||
|
||||
INSERT INTO tenant_migrations (scope, version, name)
|
||||
VALUES ('vertical-contable', 34, '034_contribuyentes_email_preferences')
|
||||
ON CONFLICT (scope, version) DO NOTHING;
|
||||
46
apps/api/src/migrations/tenant/035_tareas.sql
Normal file
46
apps/api/src/migrations/tenant/035_tareas.sql
Normal file
@@ -0,0 +1,46 @@
|
||||
-- Tareas operativas del despacho por contribuyente. Recurrentes (semanal a
|
||||
-- anual). Materialización lazy en tarea_periodos cuando el frontend lee.
|
||||
-- Solo del presente en adelante.
|
||||
|
||||
CREATE TABLE IF NOT EXISTS tareas_catalogo (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
contribuyente_id uuid NOT NULL REFERENCES contribuyentes(entidad_id) ON DELETE CASCADE,
|
||||
nombre text NOT NULL,
|
||||
descripcion text,
|
||||
recurrencia varchar(15) NOT NULL CHECK (recurrencia IN
|
||||
('semanal','quincenal','mensual','bimestral','trimestral','semestral','anual')),
|
||||
-- Para semanal/quincenal: día de la semana (1=lunes, 7=domingo)
|
||||
dia_semana int CHECK (dia_semana BETWEEN 1 AND 7),
|
||||
-- Para mensual y mayores: día del mes (1-31). Si > último día del mes,
|
||||
-- se materializa al último día (ej. 31 en febrero → 28/29).
|
||||
dia_mes int CHECK (dia_mes BETWEEN 1 AND 31),
|
||||
solo_supervisor_completa boolean DEFAULT false,
|
||||
es_default boolean DEFAULT false,
|
||||
active boolean DEFAULT true,
|
||||
orden int DEFAULT 0,
|
||||
created_at timestamptz DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS ix_tareas_catalogo_contrib ON tareas_catalogo(contribuyente_id, active);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS tarea_periodos (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
tarea_id uuid NOT NULL REFERENCES tareas_catalogo(id) ON DELETE CASCADE,
|
||||
-- '2025-W12' semanal/quincenal, '2025-01' mensual, '2025-B1' bimestral,
|
||||
-- '2025-Q1' trimestral, '2025-S1' semestral, '2025' anual.
|
||||
periodo varchar(10) NOT NULL,
|
||||
fecha_limite date NOT NULL,
|
||||
completada boolean DEFAULT false,
|
||||
completada_at timestamptz,
|
||||
completada_por uuid,
|
||||
notas text,
|
||||
created_at timestamptz DEFAULT now(),
|
||||
UNIQUE (tarea_id, periodo)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS ix_tarea_periodos_fecha ON tarea_periodos(fecha_limite);
|
||||
CREATE INDEX IF NOT EXISTS ix_tarea_periodos_completada ON tarea_periodos(completada);
|
||||
|
||||
INSERT INTO tenant_migrations (scope, version, name)
|
||||
VALUES ('vertical-contable', 35, '035_tareas')
|
||||
ON CONFLICT (scope, version) DO NOTHING;
|
||||
38
apps/api/src/migrations/tenant/036_papeleria_trabajo.sql
Normal file
38
apps/api/src/migrations/tenant/036_papeleria_trabajo.sql
Normal file
@@ -0,0 +1,38 @@
|
||||
-- Papelería de trabajo: archivos del despacho por contribuyente, organizados
|
||||
-- por mes/año y con flujo opcional de aprobación. Formatos permitidos
|
||||
-- (validados en backend): pdf, word (doc/docx), excel (xls/xlsx). Máx 5 MB
|
||||
-- por archivo (validado en backend). NO accesible para usuarios rol cliente.
|
||||
|
||||
CREATE TABLE IF NOT EXISTS papeleria_trabajo (
|
||||
id serial PRIMARY KEY,
|
||||
contribuyente_id uuid NOT NULL REFERENCES contribuyentes(entidad_id) ON DELETE CASCADE,
|
||||
nombre varchar(255) NOT NULL, -- "Reporte de cuentas Q1"
|
||||
descripcion text,
|
||||
archivo bytea NOT NULL,
|
||||
archivo_filename varchar(255) NOT NULL, -- "reporte.pdf"
|
||||
archivo_mime varchar(100) NOT NULL,
|
||||
archivo_size int NOT NULL,
|
||||
-- periodo (mes + año)
|
||||
anio int NOT NULL CHECK (anio BETWEEN 2000 AND 2100),
|
||||
mes int NOT NULL CHECK (mes BETWEEN 1 AND 12),
|
||||
-- flujo de aprobación
|
||||
requiere_aprobacion boolean NOT NULL DEFAULT false,
|
||||
estado varchar(20) CHECK (estado IS NULL OR estado IN ('pendiente','aprobado','rechazado')),
|
||||
aprobado_por uuid,
|
||||
aprobado_at timestamptz,
|
||||
comentario_rechazo text,
|
||||
subido_por uuid NOT NULL,
|
||||
created_at timestamptz DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS ix_papeleria_contrib
|
||||
ON papeleria_trabajo(contribuyente_id, created_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS ix_papeleria_periodo
|
||||
ON papeleria_trabajo(contribuyente_id, anio, mes);
|
||||
CREATE INDEX IF NOT EXISTS ix_papeleria_estado
|
||||
ON papeleria_trabajo(estado)
|
||||
WHERE estado IS NOT NULL;
|
||||
|
||||
INSERT INTO tenant_migrations (scope, version, name)
|
||||
VALUES ('vertical-contable', 36, '036_papeleria_trabajo')
|
||||
ON CONFLICT (scope, version) DO NOTHING;
|
||||
22
apps/api/src/migrations/tenant/037_activos_fijos_baja.sql
Normal file
22
apps/api/src/migrations/tenant/037_activos_fijos_baja.sql
Normal file
@@ -0,0 +1,22 @@
|
||||
-- Tracking de activos fijos dados de baja (vendidos / desechados / otro).
|
||||
-- Solo aplica a CFDIs tipo I con uso_cfdi I01-I08 cuyo receptor es el
|
||||
-- contribuyente. Una fila por CFDI dado de baja; revertir = DELETE.
|
||||
-- La pestaña "Activos Fijos" en /impuestos consulta esta tabla para
|
||||
-- detener el cómputo de deducción mensual a partir de `fecha_baja`.
|
||||
|
||||
CREATE TABLE IF NOT EXISTS activos_fijos_baja (
|
||||
id serial PRIMARY KEY,
|
||||
cfdi_id int NOT NULL REFERENCES cfdis(id) ON DELETE CASCADE,
|
||||
fecha_baja date NOT NULL,
|
||||
motivo varchar(20) NOT NULL CHECK (motivo IN ('venta','desecho','otro')),
|
||||
comentario text,
|
||||
dado_de_baja_por uuid NOT NULL,
|
||||
created_at timestamptz DEFAULT now(),
|
||||
UNIQUE (cfdi_id)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS ix_activos_fijos_baja_cfdi ON activos_fijos_baja(cfdi_id);
|
||||
|
||||
INSERT INTO tenant_migrations (scope, version, name)
|
||||
VALUES ('vertical-contable', 37, '037_activos_fijos_baja')
|
||||
ON CONFLICT (scope, version) DO NOTHING;
|
||||
@@ -0,0 +1,11 @@
|
||||
-- Permite al contador descartar conceptos de uso CFDI (ej. I06, I07) que
|
||||
-- en su contribuyente no representan adquisiciones de activos fijos sino
|
||||
-- gastos regulares (ej. servicio telefónico mensual). Default: lista vacía
|
||||
-- (todos los usos I01-I08 se consideran activos fijos como hasta ahora).
|
||||
|
||||
ALTER TABLE contribuyentes
|
||||
ADD COLUMN IF NOT EXISTS activos_fijos_usos_excluidos jsonb DEFAULT '[]'::jsonb;
|
||||
|
||||
INSERT INTO tenant_migrations (scope, version, name)
|
||||
VALUES ('vertical-contable', 38, '038_activos_fijos_usos_excluidos')
|
||||
ON CONFLICT (scope, version) DO NOTHING;
|
||||
22
apps/api/src/migrations/tenant/039_alertas_notificadas.sql
Normal file
22
apps/api/src/migrations/tenant/039_alertas_notificadas.sql
Normal file
@@ -0,0 +1,22 @@
|
||||
-- Tracking de alertas automáticas que ya fueron notificadas por email.
|
||||
-- Mecanismo idempotente: una sola email por (alerta_id, contribuyente_id).
|
||||
-- Si la alerta deja de devolverse por `generarAlertasAutomaticas`, se marca
|
||||
-- `resuelta_at`. Si vuelve a aparecer (NULL en resuelta_at), no se re-notifica
|
||||
-- — política conservadora de Option B (una sola notificación por evento).
|
||||
|
||||
CREATE TABLE IF NOT EXISTS alertas_notificadas (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
alerta_id TEXT NOT NULL,
|
||||
contribuyente_id UUID,
|
||||
notified_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
resuelta_at TIMESTAMPTZ
|
||||
);
|
||||
|
||||
-- UNIQUE compuesto con COALESCE para que NULL en contribuyente_id no rompa
|
||||
-- la dedup (alertas tenant-level vs contribuyente-específicas comparten tabla).
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS uniq_alertas_notif
|
||||
ON alertas_notificadas (alerta_id, COALESCE(contribuyente_id::text, ''));
|
||||
|
||||
-- Índice para queries del cron que filtra por contribuyente.
|
||||
CREATE INDEX IF NOT EXISTS idx_alertas_notif_contribuyente
|
||||
ON alertas_notificadas (contribuyente_id) WHERE contribuyente_id IS NOT NULL;
|
||||
@@ -0,0 +1,12 @@
|
||||
-- Tracking de notificaciones email enviadas por recordatorio en cada
|
||||
-- ventana de proximidad (3 días, 1 día, mismo día). Cada columna se llena
|
||||
-- una sola vez cuando el cron envía el email correspondiente.
|
||||
--
|
||||
-- Si el usuario edita `fecha_limite` después de haber enviado un email,
|
||||
-- las columnas previas siguen marcadas — el cron no re-notificará para
|
||||
-- ventanas ya enviadas. Decisión MVP: simple y predecible.
|
||||
|
||||
ALTER TABLE recordatorios
|
||||
ADD COLUMN IF NOT EXISTS email_3d_at TIMESTAMPTZ,
|
||||
ADD COLUMN IF NOT EXISTS email_1d_at TIMESTAMPTZ,
|
||||
ADD COLUMN IF NOT EXISTS email_0d_at TIMESTAMPTZ;
|
||||
@@ -0,0 +1,13 @@
|
||||
-- Live Secret Key por organización Facturapi (modo Live multi-RFC).
|
||||
-- Cada organización Facturapi (1:1 con contribuyente del despacho) tiene su
|
||||
-- propia sk_live_xxx generada vía PUT /v2/organizations/{id}/apikeys/live.
|
||||
-- La key se cifra con AES-256-GCM (misma derivación que FIEL_ENCRYPTION_KEY)
|
||||
-- y se guarda con IV + auth tag por componente, igual que las credenciales FIEL.
|
||||
ALTER TABLE facturapi_orgs
|
||||
ADD COLUMN IF NOT EXISTS api_key_enc bytea,
|
||||
ADD COLUMN IF NOT EXISTS api_key_iv bytea,
|
||||
ADD COLUMN IF NOT EXISTS api_key_tag bytea;
|
||||
|
||||
INSERT INTO tenant_migrations (scope, version, name)
|
||||
VALUES ('vertical-contable', 41, '041_facturapi_orgs_api_key_enc')
|
||||
ON CONFLICT (scope, version) DO NOTHING;
|
||||
15
apps/api/src/migrations/tenant/042_metricas_ncs.sql
Normal file
15
apps/api/src/migrations/tenant/042_metricas_ncs.sql
Normal file
@@ -0,0 +1,15 @@
|
||||
-- NCs Emitidas y NCs Recibidas — surface metrics nuevas en /impuestos > ISR.
|
||||
-- Persistidas en metricas_mensuales para acceso vía cache (mismo patrón que
|
||||
-- ingresos_cobrados/egresos_pagados) y disponibles para queries directas /
|
||||
-- reportes BI sin tener que recomputar desde raw CFDIs.
|
||||
--
|
||||
-- Defecto 0 — los registros existentes se exponen con valor 0 hasta que el
|
||||
-- cron de invalidaciones los recompute. El cache total se invalida al deploy
|
||||
-- vía scripts/refresh-metricas-cache.ts.
|
||||
ALTER TABLE metricas_mensuales
|
||||
ADD COLUMN IF NOT EXISTS ncs_emitidas numeric(18,2) DEFAULT 0,
|
||||
ADD COLUMN IF NOT EXISTS ncs_recibidas numeric(18,2) DEFAULT 0;
|
||||
|
||||
INSERT INTO tenant_migrations (scope, version, name)
|
||||
VALUES ('vertical-contable', 42, '042_metricas_ncs')
|
||||
ON CONFLICT (scope, version) DO NOTHING;
|
||||
@@ -0,0 +1,13 @@
|
||||
-- Gastos no deducibles por Art. 27 fracción III LISR — facturas recibidas
|
||||
-- pagadas en efectivo (forma_pago='01') con monto > $2,000. Persistido en
|
||||
-- metricas_mensuales para acceso vía cache + queries directas / reportes BI.
|
||||
--
|
||||
-- Defecto 0 — los registros existentes se exponen con 0 hasta que el cron
|
||||
-- de invalidaciones los recompute. El cache total se invalida al deploy
|
||||
-- vía scripts/refresh-metricas-cache.ts.
|
||||
ALTER TABLE metricas_mensuales
|
||||
ADD COLUMN IF NOT EXISTS gastos_no_deducibles_efectivo numeric(18,2) DEFAULT 0;
|
||||
|
||||
INSERT INTO tenant_migrations (scope, version, name)
|
||||
VALUES ('vertical-contable', 43, '043_metricas_no_deducibles')
|
||||
ON CONFLICT (scope, version) DO NOTHING;
|
||||
Reference in New Issue
Block a user