# FASE 3: Plan de Correcciones **ID:** EPIC-VAL-003 **Fecha:** 2026-01-04 **Estado:** En Progreso **Basado en:** FASE-2 (Analisis Consolidado) --- ## 1. Resumen del Plan ### 1.1 Alcance | Metrica | Cantidad | |---------|----------| | Gaps P0 a resolver | 18 | | Gaps P1 a resolver | 25 | | Total correcciones planificadas | 43 | | Archivos DDL a modificar | 8 | | Nuevas tablas a crear | 28 | | Nuevos campos a agregar | 85+ | | Nuevas funciones a crear | 15 | ### 1.2 Criterios de Priorizacion 1. **P0 (Critico):** Bloquea funcionalidad core, sin workaround 2. **P1 (Alto):** Funcionalidad importante, workaround posible 3. **P2 (Medio):** Mejora significativa, no bloquea 4. **P3 (Bajo):** Nice-to-have, puede diferirse --- ## 2. Correcciones P0 (Criticas) ### 2.1 CORE - Estados/Provincias **ID:** COR-021 **Gap:** GAP-CORE-TBL-001 **Archivo:** `02-core.sql` ```sql -- Tabla: core.states CREATE TABLE core.states ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES system.tenants(id), country_id UUID NOT NULL REFERENCES core.countries(id), name VARCHAR(100) NOT NULL, code VARCHAR(10) NOT NULL, is_active BOOLEAN DEFAULT TRUE, created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW(), UNIQUE(tenant_id, country_id, code) ); COMMENT ON TABLE core.states IS 'States/Provinces - Equivalent to res.country.state'; ``` **Dependencias:** core.countries (existe) **Impacto:** partners.state_id podra referenciar --- ### 2.2 CORE - Sistema Bancario **ID:** COR-022 **Gap:** GAP-CORE-TBL-002, GAP-CORE-TBL-003 **Archivo:** `02-core.sql` ```sql -- Tabla: core.banks CREATE TABLE core.banks ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES system.tenants(id), name VARCHAR(255) NOT NULL, bic VARCHAR(11), -- SWIFT/BIC code country_id UUID REFERENCES core.countries(id), street VARCHAR(255), city VARCHAR(100), zip VARCHAR(20), phone VARCHAR(50), email VARCHAR(255), is_active BOOLEAN DEFAULT TRUE, created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW(), UNIQUE(tenant_id, bic) ); -- Tabla: core.partner_banks CREATE TABLE core.partner_banks ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES system.tenants(id), partner_id UUID NOT NULL REFERENCES core.partners(id) ON DELETE CASCADE, bank_id UUID REFERENCES core.banks(id), acc_number VARCHAR(64) NOT NULL, acc_holder_name VARCHAR(255), sequence INTEGER DEFAULT 10, currency_id UUID REFERENCES core.currencies(id), is_active BOOLEAN DEFAULT TRUE, created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW() ); COMMENT ON TABLE core.banks IS 'Banks catalog - Equivalent to res.bank'; COMMENT ON TABLE core.partner_banks IS 'Partner bank accounts - Equivalent to res.partner.bank'; ``` **Dependencias:** core.partners, core.countries, core.currencies (existen) **Impacto:** Pagos, conciliacion bancaria --- ### 2.3 FINANCIAL - Bank Statements **ID:** COR-023 **Gap:** GAP-FIN-TBL-001, GAP-FIN-TBL-002 **Archivo:** `04-financial.sql` ```sql -- ENUM para estado de extracto CREATE TYPE financial.statement_status AS ENUM ( 'draft', 'open', 'confirm', 'cancelled' ); -- Tabla: financial.bank_statements CREATE TABLE financial.bank_statements ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES system.tenants(id), journal_id UUID NOT NULL REFERENCES financial.journals(id), name VARCHAR(100), reference VARCHAR(255), date DATE NOT NULL, date_done DATE, balance_start DECIMAL(20,6) DEFAULT 0, balance_end_real DECIMAL(20,6) DEFAULT 0, balance_end DECIMAL(20,6) GENERATED ALWAYS AS (balance_start + total_entry_encoding) STORED, total_entry_encoding DECIMAL(20,6) DEFAULT 0, status financial.statement_status DEFAULT 'draft', currency_id UUID REFERENCES core.currencies(id), is_complete BOOLEAN DEFAULT FALSE, created_by UUID REFERENCES auth.users(id), created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW() ); -- Tabla: financial.bank_statement_lines CREATE TABLE financial.bank_statement_lines ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES system.tenants(id), statement_id UUID NOT NULL REFERENCES financial.bank_statements(id) ON DELETE CASCADE, sequence INTEGER DEFAULT 10, date DATE NOT NULL, payment_ref VARCHAR(255), ref VARCHAR(255), partner_id UUID REFERENCES core.partners(id), amount DECIMAL(20,6) NOT NULL, amount_currency DECIMAL(20,6), foreign_currency_id UUID REFERENCES core.currencies(id), transaction_type VARCHAR(50), narration TEXT, is_reconciled BOOLEAN DEFAULT FALSE, partner_bank_id UUID REFERENCES core.partner_banks(id), account_number VARCHAR(64), created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW() ); COMMENT ON TABLE financial.bank_statements IS 'Bank statements - Equivalent to account.bank.statement'; COMMENT ON TABLE financial.bank_statement_lines IS 'Bank statement lines - Equivalent to account.bank.statement.line'; ``` **Dependencias:** COR-022 (partner_banks), financial.journals (existe) **Impacto:** Conciliacion bancaria, flujo de caja --- ### 2.4 FINANCIAL - Tax Repartition **ID:** COR-024 **Gap:** GAP-FIN-TBL-006 **Archivo:** `04-financial.sql` ```sql -- ENUM para tipo de factura en reparticion CREATE TYPE financial.repartition_type AS ENUM ( 'invoice', 'refund' ); -- Tabla: financial.tax_repartition_lines CREATE TABLE financial.tax_repartition_lines ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES system.tenants(id), tax_id UUID NOT NULL REFERENCES financial.taxes(id) ON DELETE CASCADE, repartition_type financial.repartition_type NOT NULL, sequence INTEGER DEFAULT 1, factor_percent DECIMAL(10,4) DEFAULT 100, account_id UUID REFERENCES financial.accounts(id), tag_ids UUID[], -- account.account.tag references use_in_tax_closing BOOLEAN DEFAULT TRUE, created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW() ); CREATE INDEX idx_tax_repartition_tax ON financial.tax_repartition_lines(tax_id); COMMENT ON TABLE financial.tax_repartition_lines IS 'Tax repartition lines - Equivalent to account.tax.repartition.line'; ``` **Dependencias:** financial.taxes, financial.accounts (existen) **Impacto:** Contabilidad fiscal correcta --- ### 2.5 INVENTORY - Stock Routes & Rules **ID:** COR-025 **Gap:** GAP-INV-TBL-001, GAP-INV-TBL-002 **Archivo:** `05-inventory.sql` ```sql -- ENUM para tipo de accion de regla CREATE TYPE inventory.rule_action AS ENUM ( 'pull', 'push', 'pull_push', 'buy', 'manufacture' ); -- ENUM para tipo de procurement CREATE TYPE inventory.procurement_type AS ENUM ( 'make_to_stock', 'make_to_order' ); -- Tabla: inventory.routes CREATE TABLE inventory.routes ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES system.tenants(id), name VARCHAR(255) NOT NULL, sequence INTEGER DEFAULT 10, is_active BOOLEAN DEFAULT TRUE, product_selectable BOOLEAN DEFAULT TRUE, product_categ_selectable BOOLEAN DEFAULT TRUE, warehouse_selectable BOOLEAN DEFAULT TRUE, supplied_wh_id UUID REFERENCES inventory.warehouses(id), supplier_wh_id UUID REFERENCES inventory.warehouses(id), company_id UUID REFERENCES core.companies(id), created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW() ); -- Tabla: inventory.stock_rules CREATE TABLE inventory.stock_rules ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES system.tenants(id), name VARCHAR(255) NOT NULL, route_id UUID NOT NULL REFERENCES inventory.routes(id) ON DELETE CASCADE, sequence INTEGER DEFAULT 20, action inventory.rule_action NOT NULL, procure_method inventory.procurement_type DEFAULT 'make_to_stock', location_src_id UUID REFERENCES inventory.locations(id), location_dest_id UUID NOT NULL REFERENCES inventory.locations(id), picking_type_id UUID REFERENCES inventory.picking_types(id), delay INTEGER DEFAULT 0, -- Lead time in days partner_address_id UUID REFERENCES core.partners(id), propagate_cancel BOOLEAN DEFAULT FALSE, propagate_carrier BOOLEAN DEFAULT TRUE, warehouse_id UUID REFERENCES inventory.warehouses(id), group_propagation_option VARCHAR(20) DEFAULT 'propagate', is_active BOOLEAN DEFAULT TRUE, created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW() ); -- Tabla de relacion producto-rutas CREATE TABLE inventory.product_routes ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), product_id UUID NOT NULL REFERENCES inventory.products(id) ON DELETE CASCADE, route_id UUID NOT NULL REFERENCES inventory.routes(id) ON DELETE CASCADE, UNIQUE(product_id, route_id) ); CREATE INDEX idx_routes_warehouse ON inventory.routes(supplied_wh_id); CREATE INDEX idx_rules_route ON inventory.stock_rules(route_id); CREATE INDEX idx_rules_locations ON inventory.stock_rules(location_src_id, location_dest_id); COMMENT ON TABLE inventory.routes IS 'Stock routes - Equivalent to stock.route'; COMMENT ON TABLE inventory.stock_rules IS 'Stock rules - Equivalent to stock.rule'; ``` **Dependencias:** inventory.warehouses, inventory.locations, inventory.picking_types (existen) **Impacto:** Automatizacion de abastecimiento --- ### 2.6 HR - Asistencias **ID:** COR-026 **Gap:** GAP-HR-TBL-001 **Archivo:** `12-hr.sql` ```sql -- Tabla: hr.attendances CREATE TABLE hr.attendances ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES system.tenants(id), employee_id UUID NOT NULL REFERENCES hr.employees(id) ON DELETE CASCADE, check_in TIMESTAMP NOT NULL, check_out TIMESTAMP, worked_hours DECIMAL(10,4) GENERATED ALWAYS AS ( CASE WHEN check_out IS NOT NULL THEN EXTRACT(EPOCH FROM (check_out - check_in)) / 3600.0 ELSE NULL END ) STORED, overtime_hours DECIMAL(10,4) DEFAULT 0, is_overtime BOOLEAN DEFAULT FALSE, notes TEXT, created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW(), CONSTRAINT valid_checkout CHECK (check_out IS NULL OR check_out > check_in) ); CREATE INDEX idx_attendances_employee ON hr.attendances(employee_id); CREATE INDEX idx_attendances_checkin ON hr.attendances(check_in); CREATE INDEX idx_attendances_date ON hr.attendances(tenant_id, DATE(check_in)); COMMENT ON TABLE hr.attendances IS 'Employee attendances - Equivalent to hr.attendance'; ``` **Dependencias:** hr.employees (existe) **Impacto:** Control de horario, nominas --- ### 2.7 HR - Ausencias/Vacaciones **ID:** COR-027 **Gap:** GAP-HR-TBL-002 **Archivo:** `12-hr.sql` ```sql -- ENUM para tipo de ausencia CREATE TYPE hr.leave_type_kind AS ENUM ( 'leave', 'allocation' ); -- ENUM para estado de ausencia CREATE TYPE hr.leave_status AS ENUM ( 'draft', 'confirm', 'validate1', 'validate', 'refuse' ); -- Tabla: hr.leave_types CREATE TABLE hr.leave_types ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES system.tenants(id), name VARCHAR(255) NOT NULL, code VARCHAR(20), leave_type hr.leave_type_kind DEFAULT 'leave', requires_allocation BOOLEAN DEFAULT TRUE, request_unit VARCHAR(20) DEFAULT 'day', -- day, half_day, hour allocation_type VARCHAR(20) DEFAULT 'no', -- no, fixed, fixed_allocation validity_start DATE, validity_stop DATE, max_leaves DECIMAL(10,2) DEFAULT 0, responsible_id UUID REFERENCES auth.users(id), color VARCHAR(20), is_active BOOLEAN DEFAULT TRUE, created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW() ); -- Tabla: hr.leaves CREATE TABLE hr.leaves ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES system.tenants(id), employee_id UUID NOT NULL REFERENCES hr.employees(id), leave_type_id UUID NOT NULL REFERENCES hr.leave_types(id), name VARCHAR(255), request_date_from DATE NOT NULL, request_date_to DATE NOT NULL, date_from TIMESTAMP NOT NULL, date_to TIMESTAMP NOT NULL, number_of_days DECIMAL(10,2) NOT NULL, number_of_hours DECIMAL(10,2), status hr.leave_status DEFAULT 'draft', notes TEXT, manager_id UUID REFERENCES hr.employees(id), first_approver_id UUID REFERENCES auth.users(id), second_approver_id UUID REFERENCES auth.users(id), validated_by UUID REFERENCES auth.users(id), validated_at TIMESTAMP, created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW() ); -- Tabla: hr.leave_allocations CREATE TABLE hr.leave_allocations ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES system.tenants(id), employee_id UUID NOT NULL REFERENCES hr.employees(id), leave_type_id UUID NOT NULL REFERENCES hr.leave_types(id), name VARCHAR(255), number_of_days DECIMAL(10,2) NOT NULL, date_from DATE, date_to DATE, status hr.leave_status DEFAULT 'draft', allocation_type VARCHAR(20) DEFAULT 'regular', -- regular, accrual notes TEXT, approved_by UUID REFERENCES auth.users(id), approved_at TIMESTAMP, created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW() ); CREATE INDEX idx_leaves_employee ON hr.leaves(employee_id); CREATE INDEX idx_leaves_dates ON hr.leaves(date_from, date_to); CREATE INDEX idx_leaves_status ON hr.leaves(status); COMMENT ON TABLE hr.leave_types IS 'Leave types - Equivalent to hr.leave.type'; COMMENT ON TABLE hr.leaves IS 'Employee leaves - Equivalent to hr.leave'; COMMENT ON TABLE hr.leave_allocations IS 'Leave allocations - Equivalent to hr.leave.allocation'; ``` **Dependencias:** hr.employees (existe) **Impacto:** Gestion de ausencias --- ## 3. Correcciones P1 (Altas) ### 3.1 FINANCIAL - Fiscal Positions **ID:** COR-028 **Gap:** GAP-FIN-TBL-003, GAP-FIN-TBL-004, GAP-FIN-TBL-005 **Archivo:** `04-financial.sql` ```sql -- Tabla: financial.fiscal_positions CREATE TABLE financial.fiscal_positions ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES system.tenants(id), name VARCHAR(255) NOT NULL, sequence INTEGER DEFAULT 10, is_active BOOLEAN DEFAULT TRUE, company_id UUID REFERENCES core.companies(id), country_id UUID REFERENCES core.countries(id), country_group_id UUID, state_ids UUID[], zip_from VARCHAR(20), zip_to VARCHAR(20), auto_apply BOOLEAN DEFAULT FALSE, vat_required BOOLEAN DEFAULT FALSE, fiscal_country_codes TEXT, note TEXT, created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW() ); -- Tabla: financial.fiscal_position_taxes CREATE TABLE financial.fiscal_position_taxes ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), fiscal_position_id UUID NOT NULL REFERENCES financial.fiscal_positions(id) ON DELETE CASCADE, tax_src_id UUID NOT NULL REFERENCES financial.taxes(id), tax_dest_id UUID REFERENCES financial.taxes(id) ); -- Tabla: financial.fiscal_position_accounts CREATE TABLE financial.fiscal_position_accounts ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), fiscal_position_id UUID NOT NULL REFERENCES financial.fiscal_positions(id) ON DELETE CASCADE, account_src_id UUID NOT NULL REFERENCES financial.accounts(id), account_dest_id UUID NOT NULL REFERENCES financial.accounts(id) ); COMMENT ON TABLE financial.fiscal_positions IS 'Fiscal positions - Equivalent to account.fiscal.position'; ``` --- ### 3.2 PURCHASE - Funciones Faltantes **ID:** COR-029 **Gap:** GAP-PUR-FUN-001, GAP-PUR-FUN-002 **Archivo:** `06-purchase.sql` ```sql -- Funcion: purchase.button_cancel() CREATE OR REPLACE FUNCTION purchase.button_cancel(p_order_id UUID) RETURNS VOID AS $$ DECLARE v_order RECORD; BEGIN SELECT * INTO v_order FROM purchase.purchase_orders WHERE id = p_order_id; IF NOT FOUND THEN RAISE EXCEPTION 'Purchase order % not found', p_order_id; END IF; IF v_order.locked THEN RAISE EXCEPTION 'Cannot cancel locked order'; END IF; IF v_order.status = 'cancelled' THEN RETURN; END IF; -- Cancel related pickings UPDATE inventory.pickings SET status = 'cancelled' WHERE origin_document_type = 'purchase_order' AND origin_document_id = p_order_id AND status != 'done'; -- Update order status UPDATE purchase.purchase_orders SET status = 'cancelled', updated_at = NOW() WHERE id = p_order_id; END; $$ LANGUAGE plpgsql; -- Funcion: purchase.button_draft() CREATE OR REPLACE FUNCTION purchase.button_draft(p_order_id UUID) RETURNS VOID AS $$ DECLARE v_order RECORD; BEGIN SELECT * INTO v_order FROM purchase.purchase_orders WHERE id = p_order_id; IF NOT FOUND THEN RAISE EXCEPTION 'Purchase order % not found', p_order_id; END IF; IF v_order.status NOT IN ('cancelled', 'sent') THEN RAISE EXCEPTION 'Can only set to draft from cancelled or sent state'; END IF; UPDATE purchase.purchase_orders SET status = 'draft', updated_at = NOW() WHERE id = p_order_id; END; $$ LANGUAGE plpgsql; COMMENT ON FUNCTION purchase.button_cancel IS 'Cancel purchase order - COR-029'; COMMENT ON FUNCTION purchase.button_draft IS 'Set purchase order to draft - COR-029'; ``` --- ### 3.3 CRM - Merge Leads **ID:** COR-030 **Gap:** GAP-CRM-FUN-001 **Archivo:** `11-crm.sql` ```sql -- Funcion: crm.merge_leads() CREATE OR REPLACE FUNCTION crm.merge_leads( p_lead_ids UUID[], p_target_lead_id UUID ) RETURNS UUID AS $$ DECLARE v_lead_id UUID; v_target RECORD; BEGIN -- Validate target exists SELECT * INTO v_target FROM crm.leads WHERE id = p_target_lead_id; IF NOT FOUND THEN RAISE EXCEPTION 'Target lead % not found', p_target_lead_id; END IF; -- Merge activities, notes, and attachments to target FOREACH v_lead_id IN ARRAY p_lead_ids LOOP IF v_lead_id != p_target_lead_id THEN -- Move activities UPDATE crm.lead_activities SET lead_id = p_target_lead_id WHERE lead_id = v_lead_id; -- Accumulate expected revenue UPDATE crm.leads t SET expected_revenue = t.expected_revenue + COALESCE( (SELECT expected_revenue FROM crm.leads WHERE id = v_lead_id), 0 ) WHERE t.id = p_target_lead_id; -- Mark as merged (soft delete) UPDATE crm.leads SET is_deleted = TRUE, merged_into_id = p_target_lead_id, updated_at = NOW() WHERE id = v_lead_id; END IF; END LOOP; RETURN p_target_lead_id; END; $$ LANGUAGE plpgsql; -- Add merged_into_id column ALTER TABLE crm.leads ADD COLUMN IF NOT EXISTS merged_into_id UUID REFERENCES crm.leads(id); COMMENT ON FUNCTION crm.merge_leads IS 'Merge multiple leads into one - COR-030'; ``` --- ### 3.4 INVENTORY - Scrap **ID:** COR-031 **Gap:** GAP-INV-TBL-003 **Archivo:** `05-inventory.sql` ```sql -- ENUM para estado de scrap CREATE TYPE inventory.scrap_status AS ENUM ( 'draft', 'done' ); -- Tabla: inventory.stock_scrap CREATE TABLE inventory.stock_scrap ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES system.tenants(id), name VARCHAR(100), product_id UUID NOT NULL REFERENCES inventory.products(id), product_uom_id UUID REFERENCES core.units_of_measure(id), lot_id UUID REFERENCES inventory.lots(id), scrap_qty DECIMAL(20,6) NOT NULL, scrap_location_id UUID NOT NULL REFERENCES inventory.locations(id), location_id UUID NOT NULL REFERENCES inventory.locations(id), move_id UUID REFERENCES inventory.stock_moves(id), picking_id UUID REFERENCES inventory.pickings(id), origin VARCHAR(255), date_done TIMESTAMP, status inventory.scrap_status DEFAULT 'draft', created_by UUID REFERENCES auth.users(id), created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW() ); -- Funcion para validar scrap CREATE OR REPLACE FUNCTION inventory.validate_scrap(p_scrap_id UUID) RETURNS UUID AS $$ DECLARE v_scrap RECORD; v_move_id UUID; BEGIN SELECT * INTO v_scrap FROM inventory.stock_scrap WHERE id = p_scrap_id; IF NOT FOUND THEN RAISE EXCEPTION 'Scrap record % not found', p_scrap_id; END IF; IF v_scrap.status = 'done' THEN RETURN v_scrap.move_id; END IF; -- Create stock move INSERT INTO inventory.stock_moves ( tenant_id, product_id, product_uom_id, quantity, location_id, location_dest_id, origin, status ) VALUES ( v_scrap.tenant_id, v_scrap.product_id, v_scrap.product_uom_id, v_scrap.scrap_qty, v_scrap.location_id, v_scrap.scrap_location_id, v_scrap.name, 'done' ) RETURNING id INTO v_move_id; -- Update scrap UPDATE inventory.stock_scrap SET status = 'done', move_id = v_move_id, date_done = NOW(), updated_at = NOW() WHERE id = p_scrap_id; RETURN v_move_id; END; $$ LANGUAGE plpgsql; COMMENT ON TABLE inventory.stock_scrap IS 'Stock scrap - Equivalent to stock.scrap'; ``` --- ### 3.5 PROJECTS - Project Updates **ID:** COR-032 **Gap:** GAP-PRJ-TBL-001 **Archivo:** `08-projects.sql` ```sql -- ENUM para estado de update CREATE TYPE projects.update_status AS ENUM ( 'on_track', 'at_risk', 'off_track', 'done' ); -- Tabla: projects.project_updates CREATE TABLE projects.project_updates ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES system.tenants(id), project_id UUID NOT NULL REFERENCES projects.projects(id) ON DELETE CASCADE, name VARCHAR(255) NOT NULL, status projects.update_status DEFAULT 'on_track', progress INTEGER CHECK (progress >= 0 AND progress <= 100), date DATE NOT NULL DEFAULT CURRENT_DATE, description TEXT, user_id UUID NOT NULL REFERENCES auth.users(id), created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW() ); CREATE INDEX idx_project_updates_project ON projects.project_updates(project_id); CREATE INDEX idx_project_updates_date ON projects.project_updates(date DESC); COMMENT ON TABLE projects.project_updates IS 'Project updates - Equivalent to project.update'; ``` --- ## 4. Correcciones P2 (Medias) ### 4.1 SALES - Order Templates **ID:** COR-033 **Gap:** Templates de venta **Archivo:** `07-sales.sql` ```sql -- Tabla: sales.order_templates CREATE TABLE sales.order_templates ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES system.tenants(id), name VARCHAR(255) NOT NULL, note TEXT, number_of_days INTEGER DEFAULT 0, require_signature BOOLEAN DEFAULT FALSE, require_payment BOOLEAN DEFAULT FALSE, prepayment_percent DECIMAL(5,2) DEFAULT 0, is_active BOOLEAN DEFAULT TRUE, created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW() ); -- Tabla: sales.order_template_lines CREATE TABLE sales.order_template_lines ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), template_id UUID NOT NULL REFERENCES sales.order_templates(id) ON DELETE CASCADE, sequence INTEGER DEFAULT 10, product_id UUID REFERENCES inventory.products(id), name TEXT, quantity DECIMAL(20,6) DEFAULT 1, product_uom_id UUID REFERENCES core.units_of_measure(id), display_type VARCHAR(20) -- line_section, line_note ); COMMENT ON TABLE sales.order_templates IS 'Sale order templates - Equivalent to sale.order.template'; ``` --- ### 4.2 CORE - Attachments **ID:** COR-034 **Gap:** GAP-CORE-TBL-004 **Archivo:** `02-core.sql` ```sql -- Tabla: core.attachments CREATE TABLE core.attachments ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES system.tenants(id), name VARCHAR(255) NOT NULL, res_model VARCHAR(255) NOT NULL, res_id UUID NOT NULL, description TEXT, type VARCHAR(20) DEFAULT 'binary', -- binary, url url VARCHAR(1024), store_fname VARCHAR(255), file_size INTEGER, checksum VARCHAR(64), mimetype VARCHAR(128), index_content TEXT, is_public BOOLEAN DEFAULT FALSE, access_token VARCHAR(64), created_by UUID REFERENCES auth.users(id), created_at TIMESTAMP DEFAULT NOW() ); CREATE INDEX idx_attachments_res ON core.attachments(res_model, res_id); CREATE INDEX idx_attachments_token ON core.attachments(access_token) WHERE access_token IS NOT NULL; COMMENT ON TABLE core.attachments IS 'File attachments - Equivalent to ir.attachment'; ``` --- ## 5. Resumen de Correcciones ### 5.1 Por Archivo | Archivo | Correcciones | Nuevas Tablas | Nuevas Funciones | |---------|--------------|---------------|------------------| | 02-core.sql | COR-021, COR-022, COR-034 | 4 | 0 | | 04-financial.sql | COR-023, COR-024, COR-028 | 6 | 0 | | 05-inventory.sql | COR-025, COR-031 | 4 | 1 | | 06-purchase.sql | COR-029 | 0 | 2 | | 07-sales.sql | COR-033 | 2 | 0 | | 08-projects.sql | COR-032 | 1 | 0 | | 11-crm.sql | COR-030 | 0 | 1 | | 12-hr.sql | COR-026, COR-027 | 4 | 0 | | **TOTAL** | **14** | **21** | **4** | ### 5.2 Orden de Ejecucion 1. **Grupo 1 (Sin dependencias):** - COR-021: core.states - COR-034: core.attachments - COR-026: hr.attendances - COR-032: projects.project_updates 2. **Grupo 2 (Depende de Grupo 1):** - COR-022: core.banks, core.partner_banks (depende de partners) - COR-027: hr.leaves (depende de employees) - COR-024: tax_repartition_lines (depende de taxes) 3. **Grupo 3 (Depende de Grupo 2):** - COR-023: bank_statements (depende de partner_banks) - COR-028: fiscal_positions (depende de taxes) - COR-025: routes, rules (depende de picking_types) 4. **Grupo 4 (Funciones):** - COR-029: purchase.button_cancel, button_draft - COR-030: crm.merge_leads - COR-031: inventory.validate_scrap 5. **Grupo 5 (Templates):** - COR-033: sales.order_templates --- ## 6. Dependencias entre Correcciones ``` COR-021 (states) ──────────────────────────────────────┐ ↓ COR-022 (banks) ──────→ COR-023 (bank_statements) ───→ Conciliacion COR-024 (tax_repartition) ──→ COR-028 (fiscal_positions) COR-025 (routes/rules) ──→ Automatizacion de inventario COR-026 (attendances) ─┬─→ COR-027 (leaves) ──→ Nominas │ └─→ Reportes HR COR-030 (merge_leads) ──→ CRM mejorado COR-031 (scrap) ──→ Gestion de mermas COR-032 (updates) ──→ Seguimiento de proyectos COR-033 (templates) ──→ Ventas agiles COR-034 (attachments) ──→ Documentos en todo el sistema ``` --- ## 7. Criterios de Aceptacion ### 7.1 Por Correccion - [ ] SQL sintacticamente valido - [ ] Indices creados para FKs - [ ] COMMENTs agregados - [ ] Constraints definidos - [ ] Compatible con multi-tenancy (tenant_id) - [ ] Sin romper dependencias existentes ### 7.2 Global - [ ] Todos los archivos DDL ejecutables en orden - [ ] No hay referencias circulares - [ ] RLS compatible con nuevas tablas --- ## 8. Proximos Pasos 1. **FASE 4:** Validar dependencias detalladamente 2. **FASE 5:** Refinar orden de ejecucion 3. **FASE 6:** Aplicar correcciones 4. **FASE 7:** Validar implementacion --- **Generado:** 2026-01-04 **Herramienta:** Claude Code **Metodologia:** SCRUM/SIMCO