Saltearse al contenido

01 - Diagrama Entidad-Relación

Advertencias entre Esquema y Eloquent

  • beneficiario se expone como hasOne en OrdenCompra, pero la migración no establece una restricción única sobre beneficiario.compra_id. Físicamente la relación puede expandirse a muchas filas; el hasOne de Eloquent solo retornará la primera.
  • orden_compras -> cuotas se aplica a nivel de FK (cuotas.orden_compra_id, nullable), pero no existe un método de relación OrdenCompra::cuotas() en app/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_lineas y descontable_descuento son 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

PadreRelaciónHijoColumna FKOn Delete
personashasOneuserspersona_idSET NULL
usershasOneclientesuser_idCASCADE
usersbelongsToManyempresaspivot: user_empresa
usersbelongsToManysucursalespivot: user_empresa (via sucursal_id)
usershasManycarritosuser_idCASCADE
usershasManylista_deseosuser_idCASCADE
usershasManyorden_comprasuser_idCASCADE
empresashasManysucursalesempresa_idCASCADE
empresashasManyproductosempresa_idCASCADE
empresasbelongsToManylineaspivot: empresa_linea
lineashasManylineas (children)linea_padre_idCASCADE
lineashasManyproductoslinea_idCASCADE
marcashasManyproductosmarca_idSET NULL
productoshasManypreciosproducto_idCASCADE
precioshasManyprecio_imagenesprecio_idCASCADE
precioshasManycarritosprecio_idCASCADE
precioshasManylista_deseosprecio_idCASCADE
precioshasManyventa_detallesprecio_idCASCADE
orden_comprashasManyventasorden_compra_idCASCADE
orden_comprashasOnebeneficiariocompra_idCASCADE
orden_comprasFK only (no Eloquent relation)cuotasorden_compra_idCASCADE
ventashasManyventa_detallesventa_idCASCADE
ventashasManycuotasventa_idCASCADE
ventasbelongsTousers (customer)user_idCASCADE
ventasbelongsTousers (employee)empleado_idSET NULL
ventasbelongsTosucursalessucursal_idCASCADE
clienteshasManyaprobar_cupo_eventoscliente_id(no onDelete declared)
postulacion_aliadosbelongsToManylineaspivot: postulacion_aliados_lineasCASCADE on both sides

Notas

  • Los Empleados son Usuarios: No existe una tabla empleados separada. Los empleados son users con roles específicos, vinculados mediante la tabla pivote user_empresa (que incluye sucursal_id).
  • Soft deletes: users (deleted_at), precios (eliminado_en — nombre de columna personalizado), descontable_descuento (eliminado_en).
  • Jerarquía auto-referenciada: lineas soporta 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) y cuotas.orden_compra_id (nullable) como llaves foráneas. El modelo Eloquent Cuota solo expone venta() — no hay un método de relación ordenCompra(), por lo que el enlace a orden_compras solo es accesible mediante consultas raw o atravesando Cuota → Venta → OrdenCompra.
  • Deriva de cardinalidad en Beneficiario: OrdenCompra::beneficiario() está definido como hasOne(Beneficiario::class, 'compra_id'), pero no existe ninguna restricción única sobre beneficiario.compra_id a 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 — web para usuarios cliente y app para 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 base Modelo. 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_eventos usa 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, pero App\Models\DescontableDescuento extiende la clase base Model de Laravel (no la clase base Modelo del proyecto) y no declara $fillable, $casts, CREATED_AT, UPDATED_AT, ni el trait SoftDeletes. Eloquent por lo tanto espera created_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 declara cliente_id con ->constrained('clientes') y sin onDelete explícito, por lo que MySQL recurre a RESTRICT para la FK. El modelo deshabilita los timestamps de Eloquent ($timestamps = false) y usa creado_en (precisión de 6 dígitos) mantenido por la base de datos vía useCurrent().

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.