01 - Diagrama Entidad-Relación
Advertencias entre Esquema y Eloquent
beneficiariose expone comohasOneenOrdenCompra, pero la migración no establece una restricción única sobrebeneficiario.compra_id. Físicamente la relación puede expandirse a muchas filas; elhasOnede Eloquent solo retornará la primera.orden_compras -> cuotasse aplica a nivel de FK (cuotas.orden_compra_id, nullable), pero no existe un método de relaciónOrdenCompra::cuotas()enapp/Models/Facturacion/OrdenCompra.php. Trátese como una relación solo a nivel de esquema, no como una ruta de Eloquent completamente cableada.postulacion_aliados_lineasydescontable_descuentoson tablas reales en el esquema; aparecen en el diagrama abajo por completitud.
Diagrama ER Mermaid
erDiagram
%% ═══════════════════════════════════════════
%% CORE USER & IDENTITY
%% ═══════════════════════════════════════════
personas {
bigint id PK
string tipo_dni
string dni
timestamp expedido_en
string lugar_expedicion
}
users {
bigint id PK
bigint persona_id FK
string nombres
string apellidos
string email UK
string telefono
string direccion
timestamp fecha_nacimiento
string password
boolean activo
string guard "default: web"
string rol
timestamp email_verified_at
timestamp deleted_at
}
clientes {
bigint id PK
bigint user_id FK "unique"
string direccion
string barrio
string ciudad
string departamento
bigint numero_contrato
int estrato
decimal cupo_asignado "20,2"
decimal cupo_disponible "20,2"
timestamp cupo_vence_en
jsonb estado_cuenta
boolean acepta_terminos_condiciones
boolean acepta_cobro_factura
timestamp registro_completado_en
string certicamara_uuid
timestamp pagare_firmado_en
timestamp puede_intentar_firmar_pagare_en
string vcard
string id_externo
string valor_promedio
string ciclo
string dia_pago
}
%% ═══════════════════════════════════════════
%% PARTNER / ALLY ENTITIES
%% ═══════════════════════════════════════════
empresas {
bigint id PK
string id_externo
string nombre
string razon_social
string tipo
string identificacion
string tipo_identificacion
string telefono
string codigo_shivam
string imagen
string banner
string estado
string canal_venta
}
sucursales {
bigint id PK
bigint empresa_id FK
string id_externo
string nombre_oficial
string nombre_interno
string direccion
string telefono
string ciudad
}
user_empresa {
bigint id PK
bigint user_id FK
bigint empresa_id FK
bigint sucursal_id FK "nullable"
timestamp creado_en
timestamp actualizado_en
}
postulacion_aliados {
bigint id PK
string nombre_completo_solicitante
string correo_electronico
string tipo_identificacion
string identificacion
string codigo_shivam
string nombre_comercial
string telefono
string estado
text causal
}
postulacion_aliados_lineas {
bigint id PK
bigint postulacion_aliado_id FK
bigint linea_id FK
timestamp created_at
timestamp updated_at
}
%% ═══════════════════════════════════════════
%% PRODUCT CATALOG
%% ═══════════════════════════════════════════
lineas {
bigint id PK
bigint linea_padre_id FK "self-ref, max 2 levels"
string nombre
string slug UK
longText descripcion
int plazo_minimo
int plazo_maximo
}
marcas {
bigint id PK
string id_externo
string nombre
string slug UK
string descripcion
string logo_photo_path
}
productos {
bigint id PK
bigint empresa_id FK
bigint linea_id FK
bigint marca_id FK
string id_externo
string slug UK
string nombre
text descripcion
string tipo_producto
string sku
string estado "default: pendiente"
jsonb caracteristicas
}
precios {
bigint id PK
bigint producto_id FK
string nombre
text descripcion
decimal precio "10,2"
bigint inventario
bigint alerta_inventario
boolean es_primario
timestamp eliminado_en "soft delete"
}
precio_imagenes {
bigint id PK
bigint precio_id FK
longText url
}
empresa_linea {
bigint id PK
bigint empresa_id FK
bigint linea_id FK
timestamp creado_en
timestamp actualizado_en
}
%% ═══════════════════════════════════════════
%% SHOPPING: CART & WISHLIST
%% ═══════════════════════════════════════════
carritos {
bigint id PK
bigint user_id FK
bigint precio_id FK
int cantidad
boolean activo
}
lista_deseos {
bigint id PK
bigint user_id FK
bigint precio_id FK
}
%% ═══════════════════════════════════════════
%% BILLING: ORDERS, SALES, INSTALLMENTS
%% ═══════════════════════════════════════════
orden_compras {
bigint id PK
bigint user_id FK
string estado "default: procesada"
bigint subtotal
float descuento_aplicado
bigint total
text observaciones
}
ventas {
bigint id PK
string id_externo
bigint orden_compra_id FK
bigint user_id FK
bigint empleado_id FK "users.id"
bigint sucursal_id FK
bigint subtotal
float descuento_aplicado
bigint total
int numero_cuotas
string causal
string estado "EstadoVenta enum"
boolean es_indeterminado
string observaciones
}
venta_detalles {
bigint id PK
bigint venta_id FK
bigint precio_id FK
float descuento_aplicado
string descriptor
bigint monto
int cantidad
}
cuotas {
bigint id PK
bigint venta_id FK "nullable"
bigint orden_compra_id FK "nullable"
int numero_cuota
decimal monto "15,2"
decimal monto_pagado "15,2"
string estado "EstadoCuota enum"
timestamp fecha_vencimiento
timestamp fecha_pago
decimal interes "15,2"
decimal monto_seguro_vida "15,2"
decimal monto_fianza "15,2"
text observaciones
}
beneficiario {
bigint id PK
bigint compra_id FK "orden_compras.id"
string primer_nombre
string primer_apellido
string segundo_apellido
string tipo_identificacion
string numero_identificacion
string parentesco
}
%% ═══════════════════════════════════════════
%% CREDIT APPROVAL
%% ═══════════════════════════════════════════
aprobar_cupo_eventos {
uuid id PK
bigint cliente_id FK
string tipo_proceso "ProcessType enum"
string evento "EventType enum"
jsonb contexto
timestamp creado_en
}
%% ═══════════════════════════════════════════
%% DISCOUNT POLYMORPHIC LINK
%% ═══════════════════════════════════════════
descontable_descuento {
bigint id PK
string tipo_descontable
bigint descontable_id
timestamp expira_en
timestamp creado_en
timestamp actualizado_en
timestamp eliminado_en "soft delete"
}
%% ═══════════════════════════════════════════
%% LOGGING
%% ═══════════════════════════════════════════
backend_request_logs {
bigint id PK
string level
text message
json context
}
%% ═══════════════════════════════════════════
%% RELATIONSHIPS
%% ═══════════════════════════════════════════
%% User & Identity
personas ||--o| users : "hasOne usuario"
users ||--o| clientes : "hasOne cliente"
users }o--o{ empresas : "belongsToMany via user_empresa"
users }o--o{ sucursales : "belongsToMany via user_empresa (sucursal_id)"
%% Partner structure
empresas ||--o{ sucursales : "hasMany"
empresas ||--o{ productos : "hasMany"
empresas }o--o{ lineas : "belongsToMany via empresa_linea"
%% Product catalog
lineas ||--o{ lineas : "self-ref lineaPadre/lineasHijas"
lineas ||--o{ productos : "hasMany"
marcas ||--o{ productos : "hasMany"
productos ||--o{ precios : "hasMany"
precios ||--o{ precio_imagenes : "hasMany imagenes"
%% Shopping
users ||--o{ carritos : "hasMany carrito"
precios ||--o{ carritos : "belongsTo"
users ||--o{ lista_deseos : "hasMany listaDeseos"
precios ||--o{ lista_deseos : "belongsTo"
%% Billing
users ||--o{ orden_compras : "hasMany compras"
orden_compras ||--o{ ventas : "hasMany"
orden_compras ||--o| beneficiario : "hasOne"
ventas ||--o{ venta_detalles : "hasMany detalles"
ventas ||--o{ cuotas : "hasMany (venta_id nullable)"
ventas }o--|| users : "belongsTo user (customer)"
ventas }o--o| users : "belongsTo empleado"
ventas }o--|| sucursales : "belongsTo"
venta_detalles }o--|| precios : "belongsTo precio"
precios ||--o{ venta_detalles : "hasMany ventaDetalles"
cuotas }o--o| orden_compras : "FK only (orden_compra_id nullable, no Eloquent relation)"
%% Credit approval
clientes ||--o{ aprobar_cupo_eventos : "hasMany events"
%% Postulaciones (pivot is named postulacion_aliados_lineas)
postulacion_aliados ||--o{ postulacion_aliados_lineas : "hasMany pivot"
lineas ||--o{ postulacion_aliados_lineas : "hasMany pivot"
postulacion_aliados }o--o{ lineas : "belongsToMany via postulacion_aliados_lineas"
Tabla Resumen de Relaciones
| Padre | Relación | Hijo | Columna FK | On Delete |
|---|---|---|---|---|
| personas | hasOne | users | persona_id | SET NULL |
| users | hasOne | clientes | user_id | CASCADE |
| users | belongsToMany | empresas | pivot: user_empresa | — |
| users | belongsToMany | sucursales | pivot: user_empresa (via sucursal_id) | — |
| users | hasMany | carritos | user_id | CASCADE |
| users | hasMany | lista_deseos | user_id | CASCADE |
| users | hasMany | orden_compras | user_id | CASCADE |
| empresas | hasMany | sucursales | empresa_id | CASCADE |
| empresas | hasMany | productos | empresa_id | CASCADE |
| empresas | belongsToMany | lineas | pivot: empresa_linea | — |
| lineas | hasMany | lineas (children) | linea_padre_id | CASCADE |
| lineas | hasMany | productos | linea_id | CASCADE |
| marcas | hasMany | productos | marca_id | SET NULL |
| productos | hasMany | precios | producto_id | CASCADE |
| precios | hasMany | precio_imagenes | precio_id | CASCADE |
| precios | hasMany | carritos | precio_id | CASCADE |
| precios | hasMany | lista_deseos | precio_id | CASCADE |
| precios | hasMany | venta_detalles | precio_id | CASCADE |
| orden_compras | hasMany | ventas | orden_compra_id | CASCADE |
| orden_compras | hasOne | beneficiario | compra_id | CASCADE |
| orden_compras | FK only (no Eloquent relation) | cuotas | orden_compra_id | CASCADE |
| ventas | hasMany | venta_detalles | venta_id | CASCADE |
| ventas | hasMany | cuotas | venta_id | CASCADE |
| ventas | belongsTo | users (customer) | user_id | CASCADE |
| ventas | belongsTo | users (employee) | empleado_id | SET NULL |
| ventas | belongsTo | sucursales | sucursal_id | CASCADE |
| clientes | hasMany | aprobar_cupo_eventos | cliente_id | (no onDelete declared) |
| postulacion_aliados | belongsToMany | lineas | pivot: postulacion_aliados_lineas | CASCADE on both sides |
Notas
- Los Empleados son Usuarios: No existe una tabla
empleadosseparada. Los empleados sonuserscon roles específicos, vinculados mediante la tabla pivoteuser_empresa(que incluyesucursal_id). - Soft deletes:
users(deleted_at),precios(eliminado_en — nombre de columna personalizado),descontable_descuento(eliminado_en). - Jerarquía auto-referenciada:
lineassoporta padre-hijo con un máximo de 2 niveles aplicado en el boot del modelo. - Doble FK en cuotas, ambas nullable: La migración declara
cuotas.venta_id(nullable) ycuotas.orden_compra_id(nullable) como llaves foráneas. El modelo EloquentCuotasolo exponeventa()— no hay un método de relaciónordenCompra(), por lo que el enlace aorden_comprassolo es accesible mediante consultas raw o atravesandoCuota → Venta → OrdenCompra. - Deriva de cardinalidad en Beneficiario:
OrdenCompra::beneficiario()está definido comohasOne(Beneficiario::class, 'compra_id'), pero no existe ninguna restricción única sobrebeneficiario.compra_ida nivel de base de datos. Físicamente se permiten múltiples beneficiarios por compra; el modelo retornará silenciosamente solo el primero. - Autenticación con doble guard: La columna
users.guard(default: web) soporta dos guards de autenticación —webpara usuarios cliente yapppara usuarios del portal de aliados/partners. - Timestamps personalizados: La mayoría de las tablas usan nombres en español (
creado_en,actualizado_en) vía la clase baseModelo. Excepciones:users,cuotas,backend_request_logs,frontend_error_logs,logs,postulacion_aliados_lineas, y las tablas de Spatie usan los valores por defecto de Laravel (created_at,updated_at). - Llaves primarias UUID: Solo
aprobar_cupo_eventosusa una llave primaria UUID; todas las demás tablas del dominio usan bigint auto-incremental. - Discrepancia de timestamps en DescontableDescuento: La migración usa
creado_en/actualizado_en/eliminado_en, peroApp\Models\DescontableDescuentoextiende la clase baseModelde Laravel (no la clase baseModelodel proyecto) y no declara$fillable,$casts,CREATED_AT,UPDATED_AT, ni el traitSoftDeletes. Eloquent por lo tanto esperacreated_at/updated_at, los cuales no existen en la tabla — cualquier escritura de Eloquent a través de este modelo fallará o ignorará silenciosamente los timestamps. - Modelo de integridad de
aprobar_cupo_eventos: La migración declaracliente_idcon->constrained('clientes')y sinonDeleteexplícito, por lo que MySQL recurre aRESTRICTpara la FK. El modelo deshabilita los timestamps de Eloquent ($timestamps = false) y usacreado_en(precisión de 6 dígitos) mantenido por la base de datos víauseCurrent().
Tablas Excluidas
Las tablas de infraestructura (sessions, cache, cache_locks, jobs, failed_jobs, job_batches, password_reset_tokens, migrations, logs, frontend_error_logs) y las tablas de permisos de Spatie (roles, permissions, model_has_roles, model_has_permissions, role_has_permissions) existen en la base de datos pero están excluidas de este diagrama ER enfocado en el dominio.