Card #5 · Modelado de base de datos

Esquema de base de datos — Sign CRM

De Firestore (documentos anidados, reglas abiertas) a PostgreSQL relacional, con aislamiento real por empresa y sin un solo ENUM fijo.

Tablas 40 (17 de catálogo + 23 de negocio) · validado contra Postgres 17 Archivos fuente schema.sql · schema.dbml
Regla de diseño

Nada de ENUM de Postgres ni listas fijas con CHECK. Todo lo que era un conjunto de valores fijos en Firestore (status, prioridad, rol, moneda...) es acá una tabla de catálogo: id, code, label, sort_order. Agregar un status o una moneda nueva es un INSERT, nunca una migración de schema — justo el problema que ya sufriste antes con enums.

Diagrama interactivo

Esta página es un mapa de lectura rápida. Para el diagrama real, con zoom y las líneas de relación dibujadas automáticamente, copiá el contenido de docs/db/schema.dbml y pegalo en dbdiagram.io — es gratis y no requiere cuenta para visualizar.

Abrir dbdiagram.io →
01

Identidad & Tenancy

Reemplaza las 4 colecciones de Firestore que duplicaban la misma información (platformUsers, platformWorkspaces, teamMembers, teamAccess) por 4 tablas limpias, sin duplicación.

users

Cuenta global de usuario — refleja la identidad de Keycloak.

id uuid = sub de Keycloak
is_superadmin bool
workspaces

Una fila por empresa/tenant.

owner_user_id → users
status_id → account_statuses
default_currency_id → currencies
workspace_members

Quién pertenece a qué workspace y con qué rol.

workspace_id → workspaces
user_id → users
role_id → roles
workspace_member_permissions

Nivel de acceso por módulo, una fila por (miembro, módulo). Módulo nuevo = INSERT en modules, nunca ALTER TABLE.

workspace_member_id → workspace_members
module_id → modules
permission_level_id → permission_levels
02

Clients

clients

Directorio de clientes de cada empresa.

workspace_id → workspaces
03

Jobs (raíz del agregado)

Cotización, precio, instalación, aprobación del cliente y estado de producción viven en una sola fila — igual que hoy viven en un solo documento de Firestore. Solo los arrays 1:muchos se volvieron tablas hijas.

jobs

Entidad central: trabajo/estimado, con precio, instalación, aprobación pública y tablero de producción todo junto.

client_id → clients  ·  status_id → job_statuses  ·  priority_id → job_priorities
currency_id → currencies  ·  installation_status_id → installation_statuses
estimate_status_id → estimate_statuses  ·  design_status_id → design_statuses
production_stage_id → production_stages
approval_token único — reemplaza el modelo actual (datos vía URL) por token validado en el servidor
job_materials

Materiales usados, qty × precio.

job_id → jobs
inventory_item_id → inventory_items
job_quote_items

Ítems del presupuesto/cotización.

job_id → jobs
job_payments

Pagos recibidos contra el trabajo.

job_id → jobs
job_images

Imágenes de diseño subidas (Cloudinary).

job_id → jobs
job_internal_notes

Bitácora de notas internas.

job_id → jobs
created_by_user_id → users
job_activity_log

Auditoría: cambios de estado, pagos, inventario.

job_id → jobs
created_by_user_id → users
job_advance_ledger

Detalle detrás del total de anticipo.

job_id → jobs
type_id → advance_entry_types
status_id → advance_entry_statuses
job_production_history

Bitácora del tablero de producción.

job_id → jobs
created_by_user_id → users
04

Inventory

inventory_items

Ítems de stock.

workspace_id → workspaces
provider_id → providers (FK real — antes era texto libre)
inventory_movements

Ledger de entrada/salida/ajuste de stock.

item_id → inventory_items
type_id → inventory_movement_types
job_id → jobs (opcional)
05

Providers & Purchasing

providers

Directorio de proveedores.

workspace_id → workspaces
purchase_orders

Órdenes de compra.

provider_id → providers
status_id → purchase_order_statuses
job_id → jobs (opcional)
purchase_order_items

Ítems de la orden de compra.

purchase_order_id → purchase_orders
inventory_item_id → inventory_items
06

Expenses

expense_categories

Categorías definidas por cada empresa (reemplaza texto libre).

workspace_id → workspaces
recurring_expenses

Plantillas que generan gastos automáticos cada mes.

workspace_id → workspaces
category_id → expense_categories
expenses

Gastos normales y generados automáticamente.

job_id → jobs (opcional)
recurring_expense_id → recurring_expenses (opcional)
expense_photos

Fotos/comprobantes.

expense_id → expenses
07

Catálogos (17 tablas, misma forma)

Todas comparten la misma estructura: id, code, label, sort_order. Esta es la pieza central que evita los ENUMs — agregar un valor nuevo es un INSERT en la tabla correspondiente.

TablaCódigos sembrados hoy
account_statusespending, active, blocked
rolesowner, admin, employee, readonly
modulesclients, jobs, expenses, inventory, providers, purchases, users, dashboard, reports, production, advance_control, backup
permission_levelsnone, view, edit, delete, manage
currenciesUSD (código ISO 4217 — agregar otra moneda es un INSERT)
job_statusesquote, approved, design, production, installation, delivered, paid, cancelled
job_prioritieslow, medium, high
installation_statusespending, confirmed, en_route, rescheduled, completed, cancelled
estimate_statusesdraft, sent, approved, changes_requested, rejected
design_statusespending, sent, approved, changes_requested
price_modesmanual, quote
discount_typespercent, fixed
production_stagesdesign, approval, production, ready_to_install, installation_scheduled, completed
advance_entry_typesmaterial, purchase, labor, expense, installation, other
advance_entry_statusespending, purchased, paid
inventory_movement_typesin, out, adjustment
purchase_order_statusesdraft, sent, partial, received, cancelled