-- /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);