From 9c3bf0f273ca99a62cfa8b660f8f0384ed43d5b9 Mon Sep 17 00:00:00 2001 From: consultoria-as Date: Tue, 31 Mar 2026 01:24:07 +0000 Subject: [PATCH] feat(pos): add tenant DB schema v1.0 with 21 tables and indexes Co-Authored-By: Claude Sonnet 4.6 --- pos/migrations/v1.0_initial.sql | 353 ++++++++++++++++++++++++++++++++ 1 file changed, 353 insertions(+) create mode 100644 pos/migrations/v1.0_initial.sql diff --git a/pos/migrations/v1.0_initial.sql b/pos/migrations/v1.0_initial.sql new file mode 100644 index 0000000..7422461 --- /dev/null +++ b/pos/migrations/v1.0_initial.sql @@ -0,0 +1,353 @@ +-- /home/Autopartes/pos/migrations/v1.0_initial.sql +-- Tenant DB schema v1.0 — 21 tables +-- Source: design spec section 10 (tenant_{id} DB) + +-- ===================== +-- SUCURSALES +-- ===================== +CREATE TABLE branches ( + id SERIAL PRIMARY KEY, + name VARCHAR(200) NOT NULL, + address TEXT, + phone VARCHAR(20), + is_active BOOLEAN DEFAULT TRUE +); + +-- ===================== +-- EMPLEADOS Y PERMISOS +-- ===================== +CREATE TABLE employees ( + id SERIAL PRIMARY KEY, + name VARCHAR(200) NOT NULL, + email VARCHAR(200), + phone VARCHAR(20), + pin VARCHAR(100), -- hashed, 4 digitos + password_hash VARCHAR(200), + role VARCHAR(20) NOT NULL, -- owner, admin, cashier, warehouse, accountant + branch_id INTEGER REFERENCES branches(id), + max_discount_pct NUMERIC(5,2) DEFAULT 0, + is_active BOOLEAN DEFAULT TRUE, + created_at TIMESTAMPTZ DEFAULT NOW() +); + +CREATE TABLE employee_permissions ( + employee_id INTEGER REFERENCES employees(id), + permission VARCHAR(100) NOT NULL, -- 'pos.sell', 'inventory.adjust', etc. + PRIMARY KEY (employee_id, permission) +); + +CREATE TABLE employee_sessions ( + id SERIAL PRIMARY KEY, + employee_id INTEGER REFERENCES employees(id), + device_id VARCHAR(200), + token VARCHAR(500) NOT NULL, + expires_at TIMESTAMPTZ NOT NULL +); + +-- ===================== +-- CLIENTES +-- ===================== +CREATE TABLE customers ( + id SERIAL PRIMARY KEY, + branch_id INTEGER REFERENCES branches(id), + name VARCHAR(300) NOT NULL, + rfc VARCHAR(13), + razon_social VARCHAR(300), + regimen_fiscal VARCHAR(10), -- codigo SAT regimen + uso_cfdi VARCHAR(10) DEFAULT 'G03', -- codigo SAT uso CFDI + cp VARCHAR(5), + email VARCHAR(200), + phone VARCHAR(20), + address TEXT, + price_tier SMALLINT DEFAULT 1 CHECK (price_tier IN (1,2,3)), -- 1=mostrador, 2=taller, 3=mayoreo + credit_limit NUMERIC(12,2) DEFAULT 0, + credit_balance NUMERIC(12,2) DEFAULT 0, -- saldo actual de credito + is_active BOOLEAN DEFAULT TRUE, + vehicle_info JSONB, -- [{make, model, year, vin, plates}] + created_at TIMESTAMPTZ DEFAULT NOW() +); + +-- ===================== +-- INVENTARIO +-- ===================== +CREATE TABLE inventory ( + id SERIAL PRIMARY KEY, + branch_id INTEGER REFERENCES branches(id), + part_number VARCHAR(100) NOT NULL, + barcode VARCHAR(100), + name VARCHAR(300) NOT NULL, + description TEXT, + category_id INTEGER, + brand VARCHAR(100), + vehicle_compatibility JSONB, + unit VARCHAR(20) DEFAULT 'PZA', + cost NUMERIC(12,2) DEFAULT 0, + price_1 NUMERIC(12,2) DEFAULT 0, -- mostrador + price_2 NUMERIC(12,2) DEFAULT 0, -- taller + price_3 NUMERIC(12,2) DEFAULT 0, -- mayoreo + tax_rate NUMERIC(5,4) DEFAULT 0.16, + min_stock INTEGER DEFAULT 0, + max_stock INTEGER DEFAULT 0, + location VARCHAR(50), -- ubicacion en almacen + image_url VARCHAR(500), + is_active BOOLEAN DEFAULT TRUE, + catalog_part_id INTEGER, -- referencia a catalogo Nexus (via API, no FK) + created_at TIMESTAMPTZ DEFAULT NOW() +); + +CREATE TABLE inventory_operations ( + id SERIAL PRIMARY KEY, + inventory_id INTEGER REFERENCES inventory(id), + branch_id INTEGER REFERENCES branches(id), + operation_type VARCHAR(20) NOT NULL, -- SALE, PURCHASE, RETURN, ADJUST, TRANSFER, INITIAL + quantity INTEGER NOT NULL, -- positivo o negativo + reference_id INTEGER, + reference_type VARCHAR(50), -- 'sale', 'purchase', 'return', etc. + cost_at_time NUMERIC(12,2), + employee_id INTEGER REFERENCES employees(id), + device_id VARCHAR(200), + notes TEXT, + created_at TIMESTAMPTZ DEFAULT NOW() +); +-- Stock actual = SUM(inventory_operations.quantity) WHERE inventory_id=X AND branch_id=Y + +-- ===================== +-- VENTAS +-- ===================== +CREATE TABLE sales ( + id SERIAL PRIMARY KEY, + branch_id INTEGER REFERENCES branches(id), + customer_id INTEGER REFERENCES customers(id), -- NULL = publico general + employee_id INTEGER REFERENCES employees(id), + register_id INTEGER, -- FK cash_registers (deferred, table below) + sale_type VARCHAR(20) NOT NULL, -- cash, credit, mixed + payment_method VARCHAR(20), -- efectivo, transferencia, tarjeta, mixto + subtotal NUMERIC(12,2) NOT NULL, + discount_total NUMERIC(12,2) DEFAULT 0, + tax_total NUMERIC(12,2) NOT NULL, + total NUMERIC(12,2) NOT NULL, + amount_paid NUMERIC(12,2) DEFAULT 0, + change_given NUMERIC(12,2) DEFAULT 0, + metodo_pago_sat VARCHAR(3), -- PUE o PPD + forma_pago_sat VARCHAR(2), -- 01, 03, 04, 99 + status VARCHAR(20) DEFAULT 'completed', -- completed, cancelled, returned + device_id VARCHAR(200), + notes TEXT, + created_at TIMESTAMPTZ DEFAULT NOW() +); + +CREATE TABLE sale_items ( + id SERIAL PRIMARY KEY, + sale_id INTEGER REFERENCES sales(id), + inventory_id INTEGER REFERENCES inventory(id), + part_number VARCHAR(100), + name VARCHAR(300), + quantity INTEGER NOT NULL, + unit_price NUMERIC(12,2) NOT NULL, -- precio al momento de la venta + unit_cost NUMERIC(12,2), -- costo al momento de la venta + discount_pct NUMERIC(5,2) DEFAULT 0, + discount_amount NUMERIC(12,2) DEFAULT 0, + tax_rate NUMERIC(5,4) DEFAULT 0.16, + tax_amount NUMERIC(12,2) DEFAULT 0, + subtotal NUMERIC(12,2) NOT NULL, + clave_prod_serv VARCHAR(10), -- clave SAT + clave_unidad VARCHAR(10) -- clave unidad SAT +); + +-- ===================== +-- COTIZACIONES +-- ===================== +CREATE TABLE quotations ( + id SERIAL PRIMARY KEY, + branch_id INTEGER REFERENCES branches(id), + customer_id INTEGER REFERENCES customers(id), + employee_id INTEGER REFERENCES employees(id), + subtotal NUMERIC(12,2) NOT NULL, + tax_total NUMERIC(12,2) NOT NULL, + total NUMERIC(12,2) NOT NULL, + status VARCHAR(20) DEFAULT 'active', -- active, converted, expired, cancelled + valid_until DATE, + converted_sale_id INTEGER REFERENCES sales(id), + notes TEXT, + created_at TIMESTAMPTZ DEFAULT NOW() +); + +CREATE TABLE quotation_items ( + id SERIAL PRIMARY KEY, + quotation_id INTEGER REFERENCES quotations(id), + inventory_id INTEGER REFERENCES inventory(id), + part_number VARCHAR(100), + name VARCHAR(300), + quantity INTEGER NOT NULL, + unit_price NUMERIC(12,2) NOT NULL, + discount_pct NUMERIC(5,2) DEFAULT 0, + tax_rate NUMERIC(5,4) DEFAULT 0.16, + subtotal NUMERIC(12,2) NOT NULL +); + +-- ===================== +-- APARTADOS (LAYAWAYS) +-- ===================== +CREATE TABLE layaways ( + id SERIAL PRIMARY KEY, + branch_id INTEGER REFERENCES branches(id), + customer_id INTEGER REFERENCES customers(id) NOT NULL, + employee_id INTEGER REFERENCES employees(id), + total NUMERIC(12,2) NOT NULL, + amount_paid NUMERIC(12,2) DEFAULT 0, + status VARCHAR(20) DEFAULT 'active', -- active, completed, cancelled + expires_at DATE, + converted_sale_id INTEGER REFERENCES sales(id), + notes TEXT, + created_at TIMESTAMPTZ DEFAULT NOW() +); + +CREATE TABLE layaway_payments ( + id SERIAL PRIMARY KEY, + layaway_id INTEGER REFERENCES layaways(id), + amount NUMERIC(12,2) NOT NULL, + payment_method VARCHAR(20), + reference VARCHAR(100), + employee_id INTEGER REFERENCES employees(id), + created_at TIMESTAMPTZ DEFAULT NOW() +); + +-- ===================== +-- CAJA REGISTRADORA +-- ===================== +CREATE TABLE cash_registers ( + id SERIAL PRIMARY KEY, + branch_id INTEGER REFERENCES branches(id), + employee_id INTEGER REFERENCES employees(id), + register_number SMALLINT NOT NULL, -- numero de caja (1, 2, 3...) + opening_amount NUMERIC(12,2) NOT NULL, -- fondo inicial + closing_amount NUMERIC(12,2), -- monto contado al cerrar + expected_amount NUMERIC(12,2), -- monto esperado calculado + difference NUMERIC(12,2), -- closing - expected + status VARCHAR(10) DEFAULT 'open', -- open, closed + opened_at TIMESTAMPTZ DEFAULT NOW(), + closed_at TIMESTAMPTZ +); + +CREATE TABLE cash_movements ( + id SERIAL PRIMARY KEY, + register_id INTEGER REFERENCES cash_registers(id), + type VARCHAR(5) NOT NULL, -- 'in' o 'out' + amount NUMERIC(12,2) NOT NULL, + reason VARCHAR(300) NOT NULL, + employee_id INTEGER REFERENCES employees(id), + created_at TIMESTAMPTZ DEFAULT NOW() +); + +-- ===================== +-- FACTURACION (CFDI QUEUE) +-- ===================== +CREATE TABLE cfdi_queue ( + id SERIAL PRIMARY KEY, + sale_id INTEGER REFERENCES sales(id), + type VARCHAR(10) NOT NULL, -- ingreso, egreso, pago + xml_unsigned TEXT, -- XML generado por POS backend + xml_signed TEXT, -- XML firmado+timbrado por Horux + uuid_fiscal VARCHAR(36), -- UUID del SAT + status VARCHAR(20) DEFAULT 'pending', -- pending, sending, stamped, failed, cancelled + retry_count SMALLINT DEFAULT 0, + provisional_folio VARCHAR(20), -- PRE-XXXXX + error_message TEXT, + cancel_motive VARCHAR(2), -- 01, 02, 03, 04 + cancel_replacement_uuid VARCHAR(36), -- UUID del CFDI sustituto (motivo 01) + created_at TIMESTAMPTZ DEFAULT NOW(), + stamped_at TIMESTAMPTZ +); + +-- ===================== +-- CONTABILIDAD +-- ===================== +CREATE TABLE accounts ( + id SERIAL PRIMARY KEY, + code VARCHAR(20) NOT NULL UNIQUE, + name VARCHAR(200) NOT NULL, + parent_id INTEGER REFERENCES accounts(id), + type VARCHAR(20) NOT NULL, -- activo, pasivo, capital, ingreso, costo, gasto + sat_code VARCHAR(20), + is_system BOOLEAN DEFAULT FALSE, -- cuentas predeterminadas no editables + is_active BOOLEAN DEFAULT TRUE +); + +CREATE TABLE journal_entries ( + id SERIAL PRIMARY KEY, + entry_number INTEGER NOT NULL, + date DATE NOT NULL, + type VARCHAR(20), -- ingreso, egreso, diario, poliza + description TEXT, + reference_type VARCHAR(50), -- sale, purchase, cash_register, etc. + reference_id INTEGER, + status VARCHAR(20) DEFAULT 'posted', -- draft, posted, cancelled + created_by INTEGER REFERENCES employees(id), + is_auto BOOLEAN DEFAULT TRUE, -- generada automaticamente + created_at TIMESTAMPTZ DEFAULT NOW() +); + +CREATE TABLE journal_entry_lines ( + id SERIAL PRIMARY KEY, + journal_entry_id INTEGER REFERENCES journal_entries(id), + account_id INTEGER REFERENCES accounts(id), + debit NUMERIC(14,2) DEFAULT 0, + credit NUMERIC(14,2) DEFAULT 0, + description TEXT +); + +CREATE TABLE fiscal_periods ( + id SERIAL PRIMARY KEY, + year SMALLINT NOT NULL, + month SMALLINT NOT NULL, + status VARCHAR(10) DEFAULT 'open', -- open, closed + closed_by INTEGER REFERENCES employees(id), + closed_at TIMESTAMPTZ, + UNIQUE (year, month) +); + +-- ===================== +-- AUDITORIA +-- ===================== +CREATE TABLE audit_log ( + id SERIAL PRIMARY KEY, + employee_id INTEGER REFERENCES employees(id), + action VARCHAR(50) NOT NULL, + entity_type VARCHAR(50), + entity_id INTEGER, + old_value JSONB, + new_value JSONB, + device_id VARCHAR(200), + ip_address VARCHAR(45), + branch_id INTEGER REFERENCES branches(id), + created_at TIMESTAMPTZ DEFAULT NOW() +); +-- INSERT-only: nunca UPDATE, nunca DELETE + +-- ===================== +-- INDEXES +-- ===================== +CREATE INDEX idx_inv_ops_inventory ON inventory_operations(inventory_id); +CREATE INDEX idx_inv_ops_branch ON inventory_operations(branch_id); +CREATE INDEX idx_inv_ops_type ON inventory_operations(operation_type); +CREATE INDEX idx_inv_ops_created ON inventory_operations(created_at); +CREATE INDEX idx_sales_branch ON sales(branch_id); +CREATE INDEX idx_sales_customer ON sales(customer_id); +CREATE INDEX idx_sales_created ON sales(created_at); +CREATE INDEX idx_sales_status ON sales(status); +CREATE INDEX idx_sale_items_sale ON sale_items(sale_id); +CREATE INDEX idx_inventory_part ON inventory(part_number); +CREATE INDEX idx_inventory_barcode ON inventory(barcode); +CREATE INDEX idx_inventory_branch ON inventory(branch_id); +CREATE INDEX idx_customers_rfc ON customers(rfc); +CREATE INDEX idx_customers_name ON customers(name); +CREATE INDEX idx_cfdi_queue_status ON cfdi_queue(status); +CREATE INDEX idx_cfdi_queue_sale ON cfdi_queue(sale_id); +CREATE INDEX idx_journal_entries_date ON journal_entries(date); +CREATE INDEX idx_journal_lines_entry ON journal_entry_lines(journal_entry_id); +CREATE INDEX idx_audit_log_action ON audit_log(action); +CREATE INDEX idx_audit_log_entity ON audit_log(entity_type, entity_id); +CREATE INDEX idx_audit_log_employee ON audit_log(employee_id); +CREATE INDEX idx_audit_log_created ON audit_log(created_at); +CREATE UNIQUE INDEX idx_inventory_branch_part ON inventory(branch_id, part_number); +CREATE INDEX idx_employee_sessions_token ON employee_sessions(token);