Saltar al contenido principal

Base de datos

PostgreSQL con extensiones uuid-ossp (claves UUID) y pg_trgm (búsquedas ILIKE indexadas). En producción corre en Supabase con RLS deshabilitado: la API controla el acceso con JWT.

Modelo de datos​

┌──────────────┐
│ users │ role: admin | advisor | client
│ company_id ──┼──────────┐
│ branch_id ──┼───────┐ │
└──────┬───────┘ │ │
user_id │ advisor_id │ │
┌─────────────────┤ │ │
▼ ▼ ▼ ▼
┌─────────────┐ ┌────────────┐ ┌──────────────────┐ ┌────────────┐
│ clients │ │ routes │◄──┤ company_branches │────►│ companies │
│ price_list ─┼─┐ │ day, │ │ route_id │ │ nit │
│ route_id ──┼─┼►│ cutoff_time│ │ advisor_id │ │ siigo_* │
│ advisor_id │ │ └────────────┘ └──────────────────┘ └────────────┘
└──────┬──────┘ │
│ ▼
│ ┌─────────────┐ ┌─────────────────────┐ ┌──────────┐
│ │ price_lists │◄───┤ product_price_lists ├───►│ products │◄── categories
│ └─────────────┘ └─────────────────────┘ └────┬─────┘
│ │ sku
▼ ▼
┌─────────────┐ ┌─────────────────┐ ┌──────────────────┐
│ quotations │──►│ quotation_items │ │ promotion_prices │──► promotions
│ code COT-… │ └─────────────────┘ └──────────────────┘ │
│ status │──► quotation_comments promotion_clients ◄──────┘
│ siigo_* │──► siigo_quote_integrations
└─────────────┘

Tablas​

Usuarios y clientes​

TablaPropósitoColumnas clave
usersCuentas de acceso de los tres rolesemail (único), password_hash (bcrypt), role, active, company_id, branch_id, siigo_seller_id, token_version (se incrementa por trigger al cambiar contraseña o desactivar; revoca refresh tokens)
clientsEl cliente: ficha comercial, con o sin usuario de loginuser_id (opcional, único), company_id, branch_id (sucursal por defecto), nit (único), email (opcional), price_list_id, route_id/advisor_id (copias de la sucursal), siigo_client_id, active
contact_phonesTeléfonos de WhatsApp de un clienteclient_id, phone_e164 (E.164, ej. +573001234567), label, is_whatsapp; único (client_id, phone_e164). Un mismo número puede estar en varios clientes
api_keysCredenciales de integraciones externaskey_prefix (único), key_hash (bcrypt), scopes, active, last_used_at, revoked_at, expires_at, replaced_by
companiesEmpresas (clientes B2B)nit (único), siigo_customer_id, siigo_sync_status (local/pending/synced/error), siigo_origin (local/siigo/bidirectional)
company_branchesSucursales de una empresacompany_id, route_id, advisor_id, latitude, longitude
Relación usuario ↔ cliente ↔ sucursal

El cliente es la fila de clients, con su propia empresa (company_id) y sucursal por defecto (branch_id). Puede tener un usuario de login (clients.user_id → users, relación 1:1; users.company_id/branch_id se mantienen sincronizados) o no tenerlo (cliente solo de WhatsApp). La sucursal define la ruta y el asesor que se usan al crear una cotización.

Catálogo y precios​

TablaPropósitoColumnas clave
categoriesCategorías de producto (en la UI: "centros de costos")name, active
productsCatálogosku (único), siigo_id (único), base_price, stock, unit, quality, image_url, description, last_sync_at
price_listsListas de preciosname (único), multiplier, is_default (solo una puede ser TRUE), active
product_price_listsPrecio específico de un producto en una listaproduct_id, price_list_id, price_list_name, price, currency; único (product_id, price_list_name)
promotionsPromocioneskind (permanent/eventual), scope (all/specific), starts_at, ends_at (nulo si es permanente), active
promotion_pricesPrecio promocional por SKU(promotion_id, sku), price
promotion_clientsClientes de una promoción specific(promotion_id, client_id)
complementary_productsRelación N:M de venta cruzada(product_id, complementary_product_id)

Rutas​

TablaPropósitoColumnas clave
routesRutas comerciales de despachooperation_days (SMALLINT[], ISO 1–7), frequency (weekly/biweekly), anchor_date (quincenales), cutoff_days_before, cutoff_time (TIME), city, advisor_id, bounds/center (JSONB), map_zone, street_from/to, carrera_from/to, active. day está deprecado

Cotizaciones​

TablaPropósitoColumnas clave
quotationsCabecera de cotizacióncode (COT-000001, único, de la secuencia quotation_code_seq), client_id, advisor_id, company_id, branch_id, status, total, notes, source (web/whatsapp), route_date, api_key_id + idempotency_key (único por key), siigo_quotation_id, siigo_url
quotation_itemsLíneasproduct_id, quantity, price_type (price_list/promotion), unit_price (precio congelado), subtotal (columna generada quantity * unit_price)
quotation_commentsComentarios en la cotizaciónauthor_id, text

Estados (quotation_status): draft, sent, pending, approved, rejected, synced, sent_to_siigo. Las cotizaciones creadas desde la web nacen en sent y las del chatbot en pending; el push exitoso a SIIGO las pasa a synced.

Webhooks​

TablaPropósitoColumnas clave
webhook_endpointsA dónde y qué eventos enviarurl, secret, previous_secret (gracia de rotación), events, sources, active, last_success_at, last_failure_at
webhook_eventsEventos ocurridos (bandeja de salida)type, entity_id, data (estado anterior/nuevo, campos cambiados)
webhook_deliveriesUn envío por evento y endpointstatus (pending/delivered/failed), attempts, next_attempt_at, last_status_code, last_error; único (event_id, endpoint_id)

Los eventos los crean triggers (webhook_emit()) sobre quotations, quotation_comments, clients, contact_phones, company_branches y routes, en la misma transacción del cambio. Si no hay endpoints suscritos, no escriben nada. Ver Webhooks.

SIIGO​

TablaPropósito
siigo_settingsFila única (id = 1): credenciales, base_url, token cacheado y estado de la última sincronización de productos
siigo_sync_logsHistorial de sincronizaciones de productos
siigo_quote_integrationsAuditoría de cada intento de envío de cotización. Índice único parcial: solo un success por cotización
siigo_customer_integrationsAuditoría de importaciones/exportaciones de clientes (operation, status, duration_ms)

Migraciones​

api/migrate.js aplica los archivos en orden y registra cada uno en la tabla _migrations, así que es seguro correrlo varias veces.

NombreArchivoQué hace
000_schemaschema.sqlEsquema base (se omite si users ya existe)
001_phase1migrations/001_phase1.sqlListas de precios, empresas, sucursales, promociones, comentarios, código de cotización
002_siigo_modulemigrations/002_siigo_module.sqlsiigo_settings y siigo_sync_logs
003_perf_indexesmigrations/003_perf_indexes.sqlÍndices faltantes y trigramas para búsqueda por SKU
004_geo_and_cleanupmigrations/004_geo_and_cleanup.sqlCoordenadas en sucursales, products.description, estados pending y sent_to_siigo
005_routes_geocolsmigrations/005_routes_geocols.sqlZona y calles/carreras en rutas
006_uq_product_price_listalter.pgsqlÍndice único en precios por lista
007_siigo_quote_integrationsmigrations/007_…Auditoría de envío de cotizaciones
008_siigo_seller_idmigrations/008_…users.siigo_seller_id
009_permanent_promotionsmigrations/009_…promotions.kind, ends_at opcional
010_catalog_sync_fixmigrations/010_…Único (product_id, price_list_name) para el upsert de sync; subtotal en ítems
011_siigo_customer_syncmigrations/011_…Campos SIIGO en companies y siigo_customer_integrations
012_fix_siigo_customer_namesmigrations/012_…Corrige nombres importados como arreglo
013_api_keysmigrations/013_…Tabla api_keys
014_chatbot_clientsmigrations/014_…clients.company_id/branch_id, email opcional, único en clients.user_id, tabla contact_phones (con backfill desde clients.phone), quotations.source y api_key_id
015_route_schedulemigrations/015_…Calendario de rutas (operation_days, frequency, anchor_date, cutoff_days_before, con backfill desde day), quotations.route_date e idempotency_key
018_webhooksmigrations/018_…Tablas de webhooks, función webhook_emit() y triggers de eventos
017_siigo_sync_modesmigrations/017_…Sync completa e incremental: siigo_sync_logs.mode e items_deactivated, siigo_settings.last_full_sync_at / last_incremental_sync_at, products.stock_control
016_credential_securitymigrations/016_…users.token_version + trigger de revocación de sesiones; api_keys.expires_at y replaced_by
seed_002_integration_testseeds/002_integration_test.sqlEscenarios para probar integraciones (solo con --seed). Ver Ambiente de pruebas
seed_001_initialseeds/001_initial.sqlDatos de desarrollo (solo con --seed). Crea usuarios con contraseñas conocidas: migrate.js lo rechaza contra una base remota salvo ALLOW_REMOTE_SEED=1. Nunca en producción
npm run migrate # solo migraciones
npm run migrate:seed # migraciones + seed
Agregar una migración

Crea database/migrations/0NN_descripcion.sql (idempotente: IF NOT EXISTS) y agrégala al arreglo STEPS de api/migrate.js. El name no se debe cambiar una vez aplicado.