erp-mecanicas-diesel/docs/03-modelo-datos/SCHEMA-SERVICE-MANAGEMENT.md

20 KiB

Schema: service_management

Descripcion

Schema que gestiona las ordenes de servicio, diagnosticos, cotizaciones y todo el flujo operativo del taller.

NOTA: Este schema depende de erp-core para autenticacion y tenants. La columna tenant_id referencia a core.tenants(id).

Tablas

service_orders

Ordenes de servicio (trabajo).

CREATE TABLE service_management.service_orders (
    id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    tenant_id       UUID NOT NULL,  -- Referencia a core.tenants

    -- Identificacion
    order_number    VARCHAR(20) NOT NULL,

    -- Relaciones
    customer_id     UUID NOT NULL,  -- Referencia a core.partners
    vehicle_id      UUID NOT NULL REFERENCES vehicle_management.vehicles(id),
    quote_id        UUID REFERENCES quotes(id),

    -- Asignacion
    assigned_to     UUID,  -- Referencia a core.users
    bay_id          UUID REFERENCES service_management.work_bays(id),

    -- Estado
    status          VARCHAR(30) DEFAULT 'received'
        CHECK (status IN ('received', 'diagnosed', 'quoted', 'approved',
                          'in_progress', 'waiting_parts', 'completed', 'delivered', 'cancelled')),

    priority        VARCHAR(20) DEFAULT 'normal'
        CHECK (priority IN ('low', 'normal', 'high', 'urgent')),

    -- Fechas
    received_at     TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    promised_at     TIMESTAMP WITH TIME ZONE,
    started_at      TIMESTAMP WITH TIME ZONE,
    completed_at    TIMESTAMP WITH TIME ZONE,
    delivered_at    TIMESTAMP WITH TIME ZONE,

    -- Kilometraje
    odometer_in     INTEGER CHECK (odometer_in >= 0),
    odometer_out    INTEGER CHECK (odometer_out >= 0),

    -- Sintomas reportados
    customer_symptoms TEXT,

    -- Totales
    labor_total     DECIMAL(12,2) DEFAULT 0 CHECK (labor_total >= 0),
    parts_total     DECIMAL(12,2) DEFAULT 0 CHECK (parts_total >= 0),
    discount        DECIMAL(12,2) DEFAULT 0 CHECK (discount >= 0),
    tax             DECIMAL(12,2) DEFAULT 0 CHECK (tax >= 0),
    grand_total     DECIMAL(12,2) DEFAULT 0 CHECK (grand_total >= 0),

    -- Notas
    internal_notes  TEXT,
    customer_notes  TEXT,

    -- Audit
    created_by      UUID,
    created_at      TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    updated_at      TIMESTAMP WITH TIME ZONE DEFAULT NOW(),

    CONSTRAINT uq_order_number UNIQUE(tenant_id, order_number),
    CONSTRAINT chk_odometer CHECK (odometer_out IS NULL OR odometer_out >= odometer_in)
);

CREATE INDEX idx_orders_tenant ON service_management.service_orders(tenant_id);
CREATE INDEX idx_orders_status ON service_management.service_orders(status);
CREATE INDEX idx_orders_vehicle ON service_management.service_orders(vehicle_id);
CREATE INDEX idx_orders_customer ON service_management.service_orders(customer_id);
CREATE INDEX idx_orders_date ON service_management.service_orders(received_at DESC);

-- RLS completo (SELECT, INSERT, UPDATE, DELETE)
SELECT create_tenant_rls_policies('service_management', 'service_orders');

work_bays

Bahias de trabajo del taller.

CREATE TABLE service_management.work_bays (
    id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    tenant_id       UUID NOT NULL,

    name            VARCHAR(50) NOT NULL,
    description     VARCHAR(200),

    -- Tipo
    bay_type        VARCHAR(50)
        CHECK (bay_type IN ('general', 'diagnostic', 'heavy_duty', 'quick_service')),

    -- Estado actual (current_order_id es NULLABLE para evitar referencia circular)
    status          VARCHAR(20) DEFAULT 'available'
        CHECK (status IN ('available', 'occupied', 'maintenance', 'reserved')),
    current_order_id UUID REFERENCES service_management.service_orders(id),

    -- Capacidad
    max_weight      DECIMAL(10,2) CHECK (max_weight > 0),
    has_lift        BOOLEAN DEFAULT FALSE,
    has_pit         BOOLEAN DEFAULT FALSE,

    is_active       BOOLEAN DEFAULT TRUE,
    sort_order      INTEGER DEFAULT 0,
    created_at      TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    updated_at      TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

CREATE INDEX idx_bays_tenant ON service_management.work_bays(tenant_id);
CREATE INDEX idx_bays_status ON service_management.work_bays(status);

SELECT create_tenant_rls_policies('service_management', 'work_bays');

order_items

Lineas de trabajo/refacciones en la orden.

CREATE TABLE service_management.order_items (
    id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    order_id        UUID NOT NULL REFERENCES service_orders(id) ON DELETE CASCADE,

    -- Tipo
    item_type       VARCHAR(20) NOT NULL
        CHECK (item_type IN ('service', 'part')),

    -- Referencia
    service_id      UUID REFERENCES service_management.services(id),
    part_id         UUID REFERENCES parts_management.parts(id),

    -- Descripcion (puede ser personalizada)
    description     VARCHAR(500) NOT NULL,

    -- Cantidades y precios
    quantity        DECIMAL(10,3) DEFAULT 1 CHECK (quantity > 0),
    unit_price      DECIMAL(12,2) NOT NULL CHECK (unit_price >= 0),
    discount_pct    DECIMAL(5,2) DEFAULT 0 CHECK (discount_pct >= 0 AND discount_pct <= 100),
    subtotal        DECIMAL(12,2) NOT NULL CHECK (subtotal >= 0),

    -- Estado
    status          VARCHAR(20) DEFAULT 'pending'
        CHECK (status IN ('pending', 'in_progress', 'completed', 'cancelled')),

    -- Para mano de obra
    estimated_hours DECIMAL(5,2) CHECK (estimated_hours > 0),
    actual_hours    DECIMAL(5,2) CHECK (actual_hours >= 0),

    -- Mecanico que realizo
    performed_by    UUID,
    completed_at    TIMESTAMP WITH TIME ZONE,

    -- Notas
    notes           TEXT,

    sort_order      INTEGER DEFAULT 0,
    created_at      TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

CREATE INDEX idx_order_items_order ON service_management.order_items(order_id);
CREATE INDEX idx_order_items_type ON service_management.order_items(item_type);

order_status_history

Historial de cambios de estado.

CREATE TABLE service_management.order_status_history (
    id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    order_id        UUID NOT NULL REFERENCES service_orders(id) ON DELETE CASCADE,

    from_status     VARCHAR(30),
    to_status       VARCHAR(30) NOT NULL,

    changed_by      UUID,
    notes           TEXT,

    created_at      TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

CREATE INDEX idx_status_history_order ON service_management.order_status_history(order_id);
CREATE INDEX idx_status_history_date ON service_management.order_status_history(created_at DESC);

services

Catalogo de servicios del taller.

CREATE TABLE service_management.services (
    id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    tenant_id       UUID NOT NULL,

    code            VARCHAR(20) NOT NULL,
    name            VARCHAR(200) NOT NULL,
    description     TEXT,

    -- Categoria
    category_id     UUID REFERENCES service_management.service_categories(id),

    -- Precios
    price           DECIMAL(12,2) NOT NULL CHECK (price >= 0),
    cost            DECIMAL(12,2) CHECK (cost >= 0),

    -- Tiempo estimado
    estimated_hours DECIMAL(5,2) CHECK (estimated_hours > 0),

    -- Estado
    is_active       BOOLEAN DEFAULT TRUE,

    created_at      TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    updated_at      TIMESTAMP WITH TIME ZONE DEFAULT NOW(),

    CONSTRAINT uq_service_code UNIQUE(tenant_id, code)
);

CREATE INDEX idx_services_tenant ON service_management.services(tenant_id);
CREATE INDEX idx_services_code ON service_management.services(code);
CREATE INDEX idx_services_category ON service_management.services(category_id);

SELECT create_tenant_rls_policies('service_management', 'services');

service_categories

Categorias de servicios.

CREATE TABLE service_management.service_categories (
    id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    tenant_id       UUID NOT NULL,

    name            VARCHAR(100) NOT NULL,
    description     VARCHAR(300),
    color           VARCHAR(7),
    icon            VARCHAR(50),

    parent_id       UUID REFERENCES service_management.service_categories(id),
    sort_order      INTEGER DEFAULT 0,

    created_at      TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    updated_at      TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

CREATE INDEX idx_service_cat_tenant ON service_management.service_categories(tenant_id);
CREATE INDEX idx_service_cat_parent ON service_management.service_categories(parent_id);

SELECT create_tenant_rls_policies('service_management', 'service_categories');

diagnostics

Diagnosticos realizados.

CREATE TABLE service_management.diagnostics (
    id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    tenant_id       UUID NOT NULL,

    order_id        UUID REFERENCES service_orders(id),
    vehicle_id      UUID NOT NULL REFERENCES vehicle_management.vehicles(id),

    -- Tipo
    diagnostic_type VARCHAR(50) NOT NULL
        CHECK (diagnostic_type IN ('scanner', 'injector_test', 'pump_test',
                                   'compression', 'turbo_test', 'other')),

    -- Equipo usado
    equipment       VARCHAR(200),

    -- Fecha y tecnico
    performed_at    TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    performed_by    UUID,

    -- Resultado general
    result          VARCHAR(20)
        CHECK (result IN ('pass', 'fail', 'needs_attention')),
    summary         TEXT,

    -- Datos crudos (JSON)
    raw_data        JSONB,

    created_at      TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

CREATE INDEX idx_diagnostics_tenant ON service_management.diagnostics(tenant_id);
CREATE INDEX idx_diagnostics_vehicle ON service_management.diagnostics(vehicle_id);
CREATE INDEX idx_diagnostics_order ON service_management.diagnostics(order_id);
CREATE INDEX idx_diagnostics_type ON service_management.diagnostics(diagnostic_type);

SELECT create_tenant_rls_policies('service_management', 'diagnostics');

diagnostic_items

Items/hallazgos del diagnostico.

CREATE TABLE service_management.diagnostic_items (
    id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    diagnostic_id   UUID NOT NULL REFERENCES diagnostics(id) ON DELETE CASCADE,

    -- Tipo de item
    item_type       VARCHAR(50) NOT NULL
        CHECK (item_type IN ('dtc_code', 'test_result', 'measurement', 'observation')),

    -- Para codigos DTC
    code            VARCHAR(20),
    description     VARCHAR(500),
    severity        VARCHAR(20)
        CHECK (severity IN ('critical', 'warning', 'info')),

    -- Para mediciones
    parameter       VARCHAR(100),
    value           DECIMAL(12,4),
    unit            VARCHAR(20),
    min_ref         DECIMAL(12,4),
    max_ref         DECIMAL(12,4),
    status          VARCHAR(20)
        CHECK (status IN ('ok', 'warning', 'fail')),

    -- Componente afectado
    component       VARCHAR(100),
    cylinder        INTEGER CHECK (cylinder > 0 AND cylinder <= 12),

    notes           TEXT,
    sort_order      INTEGER DEFAULT 0,

    created_at      TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

CREATE INDEX idx_diag_items_diagnostic ON service_management.diagnostic_items(diagnostic_id);
CREATE INDEX idx_diag_items_type ON service_management.diagnostic_items(item_type);

diagnostic_photos

Fotos de evidencia.

CREATE TABLE service_management.diagnostic_photos (
    id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    diagnostic_id   UUID NOT NULL REFERENCES diagnostics(id) ON DELETE CASCADE,

    url             VARCHAR(500) NOT NULL,
    thumbnail_url   VARCHAR(500),

    description     VARCHAR(300),
    category        VARCHAR(50)
        CHECK (category IN ('before', 'damage', 'process', 'after', 'other')),

    file_size       INTEGER CHECK (file_size > 0),
    mime_type       VARCHAR(50),

    sort_order      INTEGER DEFAULT 0,
    uploaded_by     UUID,
    created_at      TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

CREATE INDEX idx_diag_photos_diagnostic ON service_management.diagnostic_photos(diagnostic_id);

diagnostic_recommendations

Recomendaciones de reparacion.

CREATE TABLE service_management.diagnostic_recommendations (
    id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    diagnostic_id   UUID NOT NULL REFERENCES diagnostics(id) ON DELETE CASCADE,

    -- Vinculo a hallazgo
    diagnostic_item_id UUID REFERENCES diagnostic_items(id),

    -- Descripcion
    description     TEXT NOT NULL,

    -- Prioridad
    priority        VARCHAR(20) DEFAULT 'medium'
        CHECK (priority IN ('critical', 'high', 'medium', 'low')),

    urgency         VARCHAR(20) DEFAULT 'soon'
        CHECK (urgency IN ('immediate', 'soon', 'scheduled', 'preventive')),

    -- Servicio sugerido
    suggested_service_id UUID REFERENCES service_management.services(id),
    estimated_cost  DECIMAL(12,2) CHECK (estimated_cost >= 0),

    -- Estado
    status          VARCHAR(20) DEFAULT 'pending'
        CHECK (status IN ('pending', 'approved', 'declined', 'completed')),

    notes           TEXT,
    created_at      TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

CREATE INDEX idx_diag_rec_diagnostic ON service_management.diagnostic_recommendations(diagnostic_id);
CREATE INDEX idx_diag_rec_status ON service_management.diagnostic_recommendations(status);

quotes

Cotizaciones.

CREATE TABLE service_management.quotes (
    id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    tenant_id       UUID NOT NULL,

    -- Identificacion
    quote_number    VARCHAR(20) NOT NULL,

    -- Relaciones
    customer_id     UUID NOT NULL,
    vehicle_id      UUID NOT NULL REFERENCES vehicle_management.vehicles(id),
    diagnostic_id   UUID REFERENCES diagnostics(id),

    -- Estado
    status          VARCHAR(20) DEFAULT 'draft'
        CHECK (status IN ('draft', 'sent', 'viewed', 'approved', 'rejected', 'expired', 'converted')),

    -- Fechas
    created_at      TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    sent_at         TIMESTAMP WITH TIME ZONE,
    viewed_at       TIMESTAMP WITH TIME ZONE,
    responded_at    TIMESTAMP WITH TIME ZONE,
    expires_at      TIMESTAMP WITH TIME ZONE,

    -- Totales
    labor_total     DECIMAL(12,2) DEFAULT 0 CHECK (labor_total >= 0),
    parts_total     DECIMAL(12,2) DEFAULT 0 CHECK (parts_total >= 0),
    discount        DECIMAL(12,2) DEFAULT 0 CHECK (discount >= 0),
    discount_reason VARCHAR(200),
    tax             DECIMAL(12,2) DEFAULT 0 CHECK (tax >= 0),
    grand_total     DECIMAL(12,2) DEFAULT 0 CHECK (grand_total >= 0),

    -- Vigencia
    validity_days   INTEGER DEFAULT 15 CHECK (validity_days > 0),

    -- Terminos
    terms           TEXT,
    notes           TEXT,

    -- Conversion
    converted_order_id UUID REFERENCES service_orders(id),

    -- Aprobacion digital
    approved_by_name    VARCHAR(200),
    approval_signature  TEXT,
    approval_ip         INET,

    -- Audit
    created_by      UUID,
    updated_at      TIMESTAMP WITH TIME ZONE DEFAULT NOW(),

    CONSTRAINT uq_quote_number UNIQUE(tenant_id, quote_number)
);

CREATE INDEX idx_quotes_tenant ON service_management.quotes(tenant_id);
CREATE INDEX idx_quotes_status ON service_management.quotes(status);
CREATE INDEX idx_quotes_customer ON service_management.quotes(customer_id);
CREATE INDEX idx_quotes_date ON service_management.quotes(created_at DESC);

SELECT create_tenant_rls_policies('service_management', 'quotes');

quote_items

Lineas de cotizacion.

CREATE TABLE service_management.quote_items (
    id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    quote_id        UUID NOT NULL REFERENCES quotes(id) ON DELETE CASCADE,

    -- Tipo
    item_type       VARCHAR(20) NOT NULL
        CHECK (item_type IN ('service', 'part')),

    -- Referencia
    service_id      UUID REFERENCES service_management.services(id),
    part_id         UUID REFERENCES parts_management.parts(id),

    description     VARCHAR(500) NOT NULL,

    quantity        DECIMAL(10,3) DEFAULT 1 CHECK (quantity > 0),
    unit_price      DECIMAL(12,2) NOT NULL CHECK (unit_price >= 0),
    discount_pct    DECIMAL(5,2) DEFAULT 0 CHECK (discount_pct >= 0 AND discount_pct <= 100),
    subtotal        DECIMAL(12,2) NOT NULL CHECK (subtotal >= 0),

    -- Para conversion parcial
    is_approved     BOOLEAN DEFAULT TRUE,

    sort_order      INTEGER DEFAULT 0,
    created_at      TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

CREATE INDEX idx_quote_items_quote ON service_management.quote_items(quote_id);

quote_tracking

Tracking de cotizaciones enviadas.

CREATE TABLE service_management.quote_tracking (
    id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    quote_id        UUID NOT NULL REFERENCES quotes(id) ON DELETE CASCADE,

    event_type      VARCHAR(30) NOT NULL
        CHECK (event_type IN ('sent_email', 'sent_whatsapp', 'opened', 'link_clicked', 'approved', 'rejected')),

    channel         VARCHAR(20)
        CHECK (channel IN ('email', 'whatsapp', 'link', 'sms')),

    ip_address      INET,
    user_agent      TEXT,
    device_type     VARCHAR(20),

    created_at      TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

CREATE INDEX idx_quote_tracking_quote ON service_management.quote_tracking(quote_id);
CREATE INDEX idx_quote_tracking_date ON service_management.quote_tracking(created_at DESC);

quote_followups

Seguimiento de cotizaciones.

CREATE TABLE service_management.quote_followups (
    id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    quote_id        UUID NOT NULL REFERENCES quotes(id) ON DELETE CASCADE,

    action          VARCHAR(100) NOT NULL,
    notes           TEXT,

    next_action     VARCHAR(100),
    next_action_at  TIMESTAMP WITH TIME ZONE,

    created_by      UUID,
    created_at      TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

CREATE INDEX idx_quote_followups_quote ON service_management.quote_followups(quote_id);
CREATE INDEX idx_quote_followups_next ON service_management.quote_followups(next_action_at);

test_types

Tipos de prueba configurables.

CREATE TABLE service_management.test_types (
    id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    tenant_id       UUID,  -- NULL = tipo global del sistema

    name            VARCHAR(100) NOT NULL,
    description     TEXT,

    component_type  VARCHAR(50)
        CHECK (component_type IN ('injector', 'pump', 'turbo', 'engine', 'electrical', 'other')),

    is_system       BOOLEAN DEFAULT FALSE,
    is_active       BOOLEAN DEFAULT TRUE,

    created_at      TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

CREATE INDEX idx_test_types_tenant ON service_management.test_types(tenant_id);
CREATE INDEX idx_test_types_component ON service_management.test_types(component_type);

test_parameters

Parametros de prueba.

CREATE TABLE service_management.test_parameters (
    id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    test_type_id    UUID NOT NULL REFERENCES test_types(id) ON DELETE CASCADE,

    name            VARCHAR(100) NOT NULL,
    unit            VARCHAR(20),
    data_type       VARCHAR(20) DEFAULT 'numeric'
        CHECK (data_type IN ('numeric', 'boolean', 'text', 'select')),

    sort_order      INTEGER DEFAULT 0,
    created_at      TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

CREATE INDEX idx_test_params_type ON service_management.test_parameters(test_type_id);

parameter_references

Valores de referencia por motor.

CREATE TABLE service_management.parameter_references (
    id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),

    parameter_id    UUID NOT NULL REFERENCES test_parameters(id) ON DELETE CASCADE,
    engine_model_id UUID REFERENCES vehicle_management.engine_catalog(id),

    min_value       DECIMAL(12,4),
    max_value       DECIMAL(12,4),
    nominal_value   DECIMAL(12,4),
    tolerance_pct   DECIMAL(5,2) CHECK (tolerance_pct >= 0),

    source          VARCHAR(200),

    created_at      TIMESTAMP WITH TIME ZONE DEFAULT NOW(),

    CONSTRAINT uq_param_engine UNIQUE(parameter_id, engine_model_id),
    CONSTRAINT chk_min_max CHECK (max_value IS NULL OR min_value IS NULL OR max_value >= min_value)
);

CREATE INDEX idx_param_refs_param ON service_management.parameter_references(parameter_id);
CREATE INDEX idx_param_refs_engine ON service_management.parameter_references(engine_model_id);

Relacion con erp-core

Este schema utiliza las siguientes referencias a erp-core:

Columna Referencia erp-core
tenant_id core.tenants(id)
customer_id core.partners(id)
created_by, assigned_to, performed_by auth.users(id)

Creado por: Requirements-Analyst Fecha: 2025-12-06 Actualizado: 2025-12-06 (Correccion tenant_id, CHECK constraints, RLS completo)