06 - Diagrama de Arquitectura
Validated Corrections
- La arquitectura viva de ventas no es principalmente event-driven por
VentaCompletada/VentaCancelada. El camino real mas importante esCerticamaraController -> ValidarPagareDigital -> ProcesarPagareDigital -> GenerarCreditoDeVenta. - El diagrama mezcla en exceso los pipelines
webyapi. En runtime,HandleAppearance,HandleInertiaRequestsyAddLinkHeadersForPreloadedAssetsse agregan al grupoweb, no al grupoapi. Spatie Permissionesta instalado y configurado, pero no es el mecanismo principal de autorizacion efectiva del proyecto actual; la mayoria del control visible se hace con el camporoly checks inline.- El bloque de APIs externas debe leerse con dos integraciones adicionales como piezas criticas del sistema: EMCALI y Core Credito, no solo TransUnion / Experian / DataCredito / Certicamara.
- El flujo documentado de compra y billing en este archivo debe tomarse como una aproximacion de alto nivel, no como descripcion exacta del runtime actual.
- Conteo revisado para presentaciones:
app/Modelscontiene 22 archivos, pero uno esModelo(base compartida);app/Jobscontiene 6 clases, de las cuales 5 son realmente queued;app/Notificationscontiene 7 clases custom y no debe confundirse con el total de flujos de correo.
1. Arquitectura en Capas del Backend
graph TB
subgraph "Puntos de Entrada"
HTTP["Petición HTTP"]
CLI["Artisan CLI"]
SCHEDULE["Scheduler (cada 15m)"]
end
subgraph "Capa de Enrutamiento"
direction LR
WEB["routes/web.php"]
API["routes/api.php"]
ALLY_WEB["routes/ally/web.php"]
ALLY_AUTH["routes/ally/auth.php"]
AUTH["routes/auth.php"]
CONSOLE["routes/console.php"]
end
subgraph "Stack de Middleware"
direction LR
GLOBAL["Middleware Web Global<br/>HandleAppearance<br/>HandleInertiaRequests<br/>AddLinkHeadersForPreloadedAssets"]
CUSTOM["Middleware Custom<br/>Authenticate (multi-guard)<br/>ConsultarCupoDelCliente<br/>EnsureClienteRegistroCompleto<br/>VerificarClientePresentaMora<br/>CheckIntentosLimiteDiarios"]
SPATIE["Spatie Permission<br/>RoleMiddleware<br/>PermissionMiddleware<br/>RoleOrPermissionMiddleware"]
end
subgraph "Capa de Controladores"
direction LR
CTRL_MARKET["Controladores Market<br/>ProductoController<br/>CarritoController<br/>ListaDeseoController<br/>LineaController<br/>MarcaController<br/>OrdenCompraController<br/>VentaController (Market)<br/>DownloadLineaController<br/>DownloadMarcaController<br/>DownloadProductoMockController<br/>PrecioController"]
CTRL_ALIADO["Controladores Aliado<br/>DashboardController<br/>VentaController<br/>EmpleadoController<br/>EmpresaController<br/>SucursalController<br/>PostulacionController<br/>ClienteController<br/>UserController<br/>VentaReporteComercialController"]
CTRL_AUTH["Controladores Auth<br/>Auth/AuthenticatedSession<br/>Auth/RegisteredUser<br/>AliadoAuth/AuthenticatedSession<br/>AliadoAuth/RegisteredUser"]
CTRL_CREDIT["Controladores de Crédito<br/>AprobarCupoController<br/>CompletarRegistroController<br/>ConsultarCupoController<br/>VerificarLimiteIntentosController"]
CTRL_API["Controladores API<br/>DataCreditoController"]
CTRL_WEBHOOK["Controladores Webhook<br/>CerticamaraController"]
CTRL_OTHER["Otros Controladores<br/>ContactoController<br/>DashboardController<br/>ErrorLogController"]
end
subgraph "Form Requests"
FREQ["Requests Validados<br/>LoginRequest<br/>CrearVentaClienteRequest<br/>CrearVentaRequest<br/>ProfileUpdateRequest<br/>VentaDetalle Requests"]
end
subgraph "Capa de Servicios (29 servicios)"
direction LR
SVC_CORE["Servicios Core<br/>UserService<br/>ClienteService<br/>PersonaService<br/>EmpresaService<br/>SucursalService"]
SVC_MARKET["Servicios Market<br/>ProductoService<br/>PrecioService<br/>PrecioImagenService<br/>MarcaService<br/>LineaService<br/>CarritoService<br/>ListaDeseoService<br/>SpreadSheetService"]
SVC_BILLING["Servicios de Facturación<br/>VentaService<br/>OrdenCompraService<br/>CreditoService<br/>CoreCreditoService<br/>PlanCreditoService<br/>VentaReporteService<br/>DashboardService"]
SVC_EXTERNAL["Servicios de Integración Externa<br/>AprobarCupoService<br/>LegalCheckService<br/>IdentityValidationService<br/>HDCValidationService<br/>DataCreditoService<br/>CerticamaraService<br/>EmcaliMembresiaService<br/>ExtenderCupoService<br/>PostulacionService"]
end
subgraph "Capa de DTOs (39 DTOs en 18 dominios)"
DTO["Objetos de Transferencia de Datos<br/>Crear* / Actualizar* / *Result<br/>AprobarCupo | Carrito | Cliente<br/>Cuota | EmcaliMembresia | Empresa<br/>IdentityValidation | LegalCheck<br/>ListaDeseos | Marca | Persona<br/>PlanCredito | Postulaciones | Precio<br/>Producto | User | Venta | VentaDetalle"]
end
subgraph "Capa de Modelos (22 archivos / 21 modelos de dominio + 1 modelo base)"
direction LR
MDL_CORE["Modelos Core<br/>User<br/>Persona<br/>Cliente<br/>Modelo (base)"]
MDL_PARTNER["Modelos de Partner<br/>Empresa<br/>Sucursal<br/>PostulacionAliado"]
MDL_CATALOG["Modelos de Catálogo<br/>Producto<br/>Precio<br/>PrecioImagene<br/>Linea<br/>Marca"]
MDL_SHOP["Modelos de Compra<br/>Carrito<br/>ListaDeseo"]
MDL_BILLING["Modelos de Facturación<br/>OrdenCompra<br/>Venta<br/>VentaDetalle<br/>Cuota<br/>Beneficiario"]
MDL_OTHER["Otros Modelos<br/>AprobarCupoEvento<br/>BackendRequestLog<br/>DescontableDescuento"]
end
subgraph "Aspectos Transversales"
EVENTS["Eventos y Listeners (6 eventos, 6 listeners)<br/>PostulacionDeAliadoCreada -> NotificarNuevaPostulacion<br/>PostulacionDeAliadoAprobada -> EnviarInvitacionDeRegistro<br/>PostulacionDeAliadoRechazadaODescartada -> NotificarRechazODescarte<br/>ValidationLog -> StoreValidationLog<br/>VentaCompletada -> VentaCompletadaListener (evento nunca despachado)<br/>VentaCancelada -> VentaCanceladaListener (evento nunca despachado)"]
JOBS["Jobs (6 clases / 5 encolados + 1 programado)<br/>ProcesarOrdenesAbandonadas<br/>GenerarCreditoDeVenta<br/>ProcesarPagareDigital<br/>ValidarPagareDigital<br/>IniciarCargaMasivaProducto<br/>ProcesarFilaProducto"]
NOTIF["Notificaciones (7 custom)<br/>Reseteo de Contraseña (Cliente y Aliado)<br/>Correos del ciclo de Postulación"]
COMMANDS["Comandos Artisan (10)<br/>Importadores CSV (6)<br/>CrearClientesEnSistemaCredito<br/>MarcarCuotasVencidas<br/>SincronizarLineasEmpresa<br/>UpdateUserRoles"]
end
subgraph "APIs Externas"
EXT_TU["API TransUnion"]
EXT_EXP["API Experian CrossCore"]
EXT_DC["API DataCredito"]
EXT_CC["API Certicamara"]
EXT_EMC["API EMCALI"]
EXT_CORE["API Core Credito"]
end
subgraph "Base de Datos"
DB[(MySQL<br/>40 tablas — dominio + Spatie + infra Laravel)]
end
HTTP --> WEB & API & ALLY_WEB & ALLY_AUTH & AUTH
CLI --> CONSOLE
SCHEDULE --> JOBS
WEB & ALLY_WEB & ALLY_AUTH & AUTH --> GLOBAL --> CUSTOM --> SPATIE
API --> CUSTOM
SPATIE --> CTRL_MARKET & CTRL_ALIADO & CTRL_AUTH & CTRL_CREDIT & CTRL_OTHER
CUSTOM --> CTRL_API & CTRL_WEBHOOK
CTRL_MARKET & CTRL_ALIADO & CTRL_AUTH & CTRL_CREDIT --> FREQ
FREQ --> SVC_CORE & SVC_MARKET & SVC_BILLING & SVC_EXTERNAL
CTRL_API --> SVC_EXTERNAL
SVC_CORE & SVC_MARKET & SVC_BILLING & SVC_EXTERNAL --> DTO
DTO --> MDL_CORE & MDL_PARTNER & MDL_CATALOG & MDL_SHOP & MDL_BILLING & MDL_OTHER
MDL_CORE & MDL_PARTNER & MDL_CATALOG & MDL_SHOP & MDL_BILLING & MDL_OTHER --> DB
SVC_BILLING --> EVENTS
SVC_BILLING --> EXT_CORE
SVC_EXTERNAL --> EXT_TU & EXT_EXP & EXT_DC & EXT_CC & EXT_EMC
EVENTS --> NOTIF
JOBS --> SVC_BILLING & SVC_MARKET
CONSOLE --> COMMANDS
COMMANDS --> SVC_CORE & SVC_MARKET
2. Arquitectura del Frontend
graph TB
subgraph "Sistema de Build"
VITE["Vite + Plugins<br/>laravel-vite-plugin<br/>@vitejs/plugin-vue<br/>@tailwindcss/vite"]
SSR["Entry SSR<br/>resources/js/ssr.ts"]
end
subgraph "Bootstrap de la Aplicación"
ENTRY["resources/js/app.ts"]
CSS["resources/css/app.css<br/>Tailwind CSS"]
PLUGINS["Plugins Vue<br/>Plugin InertiaJS<br/>ZiggyVue (helper de rutas)<br/>vue3-toastify<br/>plugin errorHandling"]
GLOBALS["Configuración Global<br/>$formatCOP (moneda)<br/>emitter (bus de eventos mitt)<br/>defaults de axios (CSRF, timeout)"]
end
subgraph "Puente Inertia.js"
INERTIA["Adaptador Inertia<br/>createInertiaApp()<br/>resolvePageComponent()<br/>Props Compartidos desde Laravel"]
SHARED["Datos Compartidos (HandleInertiaRequests)<br/>auth.user | auth.role | clienteData | carritoData<br/>ziggy (rutas) | lineas | brands_allied<br/>links | configuración de credito | user_stats<br/>popular_categories | empresa"]
end
subgraph "Layouts (8)"
LAY["Layouts de Página<br/>MarketLayout (storefront)<br/>AllyLayout (dashboard de aliado)<br/>AppLayout (usuario autenticado)<br/>AuthLayout (login/registro)<br/>ErrorLayout<br/>app/ | auth/ | settings/"]
end
subgraph "Páginas (por dominio)"
direction LR
PG_PUBLIC["Páginas Públicas<br/>Index (home)<br/>home/<br/>product/<br/>product/Search/<br/>ConsultarCupo/<br/>Simulator/<br/>ErrorPage"]
PG_AUTH["Páginas Auth<br/>auth/ (login, registro,<br/>reseteo de contraseña, verify)"]
PG_USER["Páginas de Cliente<br/>Checkout/<br/>Compras/<br/>Wishlist/<br/>user/<br/>settings/"]
PG_ALLY["Páginas de Aliado<br/>ally/auth/ (login, registro)<br/>ally/ventas/ (+ create/)<br/>ally/productos/ (+ crear/)<br/>ally/marcas/<br/>ally/empresas/<br/>ally/sucursales/<br/>ally/empleados/<br/>ally/usuarios/<br/>ally/postulaciones/<br/>ally/pedidos/"]
end
subgraph "Componentes Reutilizables"
direction LR
COMP_SHELL["Componentes Shell<br/>AppShell | AppHeader<br/>AppSidebar | AppContent<br/>AppSidebarHeader<br/>AppLogo | AppLogoIcon"]
COMP_NAV["Navegación<br/>NavMain | NavUser<br/>NavFooter | Breadcrumbs<br/>UserInfo | UserMenuContent"]
COMP_DOMAIN["Componentes de Dominio<br/>Cart/ | Header/ | Footer/<br/>Auth/ | form/ | ui/<br/>ContactModal"]
COMP_UI["UI Base (ui/)<br/>Primitivas estilo Shadcn-vue<br/>Icon | Loader<br/>InputError | TextLink<br/>Heading | HeadingSmall"]
end
subgraph "Composables y Utilidades"
COMPOSABLES["Composables<br/>useAppearance (tema)<br/>useInitials (avatar)<br/>useWishlist"]
LIB["Utilidades Lib<br/>credit.ts (cálculo de cuotas)<br/>format.ts (formatCOP)<br/>formatters.ts<br/>utils.ts (helper cn)"]
TYPES["Tipos TypeScript<br/>global.d.ts | globals.d.ts<br/>index.d.ts | ziggy.d.ts<br/>vue-sfc.d.ts | vue-shims.d.ts"]
end
VITE --> ENTRY & SSR
ENTRY --> CSS
ENTRY --> PLUGINS --> INERTIA
ENTRY --> GLOBALS
INERTIA --> SHARED
INERTIA --> LAY
LAY --> PG_PUBLIC & PG_AUTH & PG_USER & PG_ALLY
PG_PUBLIC & PG_AUTH & PG_USER & PG_ALLY --> COMP_SHELL & COMP_NAV & COMP_DOMAIN & COMP_UI
PG_PUBLIC & PG_AUTH & PG_USER & PG_ALLY --> COMPOSABLES & LIB
COMPOSABLES & LIB --> TYPES
3. Mapeo de Directorios a Capas
| Capa Arquitectónica | Directorio | Descripción |
|---|---|---|
| Routing | routes/web.php | Rutas web del cliente (públicas + auth) |
routes/auth.php | Rutas de autenticación del cliente | |
routes/ally/web.php | Rutas autenticadas del aliado | |
routes/ally/auth.php | Rutas de auth del aliado | |
routes/api.php | Endpoints API (webhooks, externos) | |
routes/console.php | Comandos programados | |
routes/settings.php | Rutas de configuración del usuario | |
| Middleware | app/Http/Middleware/ | 7 clases de middleware custom |
| Controllers | app/Http/Controllers/Market/ | Storefront (productos, carrito, wishlist, órdenes, ventas) |
app/Http/Controllers/Aliado/ | Dashboard del aliado (9 controladores) | |
app/Http/Controllers/AliadoAuth/ | Autenticación del aliado (4 controladores) | |
app/Http/Controllers/Auth/ | Autenticación del cliente (8 controladores) | |
app/Http/Controllers/AprobarCliente/ | Flujo de aprobación de crédito (4 controladores) | |
app/Http/Controllers/Api/ | Endpoints API (DataCredito) | |
app/Http/Controllers/Webhooks/ | Webhooks entrantes (Certicamara) | |
app/Http/Controllers/Settings/ | Gestión de perfil y contraseña | |
| Form Requests | app/Http/Requests/ | Validación de entrada (6 clases de request) |
| Services | app/Services/ | Lógica de negocio (29 clases de servicio, namespace plano) |
| DTOs | app/DTOs/ | Objetos de transferencia de datos (39 clases en 18 subdirectorios) |
| Models | app/Models/ | 22 archivos en total (21 modelos de dominio + Modelo base, sub-namespace Facturacion/) |
| Enums | app/Enum/ | Enums PHP (12 enums: AprobarCupo/, Facturacion/, genéricos) |
| Events | app/Events/ | Eventos de dominio (6 eventos: facturación + postulacion) |
| Listeners | app/Listeners/ | Manejadores de eventos (6 listeners) |
| Jobs | app/Jobs/ | 6 clases de job en total (5 encolados + 1 programado) |
| Notifications | app/Notifications/ | 7 clases de notificación custom (solo canal mail) |
app/Mail/ | Mailable (TicketSoporte) | |
| Commands | app/Console/Commands/ | Comandos Artisan (10: importadores CSV, sync, roles) |
| Exceptions | app/Exceptions/ | Manejo de excepciones custom (2 clases) |
| Logging | app/Logging/ | Canales de log custom (3 loggers de base de datos) |
| Traits | app/Traits/ | Traits compartidos (ResultTrait) |
| Rules | app/Rules/ | Reglas de validación custom (CuotasEnRangoDePlazos) |
| Providers | app/Providers/ | Service provider (registro de macro HTTP) |
| Vue Pages | resources/js/pages/ | Componentes de página Inertia (13 dominios de página) |
| Vue Layouts | resources/js/layouts/ | 5 layouts raíz + 3 directorios de sub-layout |
| Vue Components | resources/js/components/ | Componentes reutilizables (shell, nav, dominio, ui) |
| Composables | resources/js/composables/ | Funciones de composición Vue (3 composables) |
| Plugins | resources/js/plugins/ | Plugins Vue (manejo de errores + interceptor) |
| Lib/Utils | resources/js/lib/ | Funciones de utilidad puras (credit, format, utils) |
| Types | resources/js/types/ | Definiciones de tipos TypeScript (6 archivos) |
4. Flujo de Datos Request-Response
4.1 Request Web Típico (Cliente navegando un producto)
Browser GET /productos/42 | v[Laravel Router] -- matches route --> ProductoController@show | v[Global Middleware Stack] HandleAppearance -- sets theme cookie HandleInertiaRequests -- prepares shared props (auth, lineas, brands, carrito, etc.) AddLinkHeadersForPreloadedAssets | v[ProductoController::show($id)] |-- calls ProductoService::getProducto($id) | |-- queries Producto model with relations (precios, imagenes, linea, marca, empresa) | |-- returns Eloquent model | |-- returns Inertia::render('product/Detail', ['producto' => $producto]) | v[Inertia Response] First visit: Full HTML page with serialized props + Vite assets Subsequent: JSON partial with only page component + props (XHR) | v[Vue Frontend] Inertia resolves --> pages/product/Detail.vue Layout wrapper --> MarketLayout.vue Renders with shared props (auth, carrito, lineas) + page props (producto)4.2 Acción Autenticada (Cliente creando una orden y firmando el pagaré)
La cadena viva de facturación corre en dos fases: un checkout síncrono que registra el pagaré en Certicámara, y un pipeline asíncrono dirigido por webhook que crea el crédito una vez que el cliente firma.
Phase 1 — Checkout (synchronous)Browser POST /mis-compras (with CSRF token + session cookie) | v[Middleware: auth, cliente_registro_completo, verificar_cliente_presenta_mora] -- Authenticate verifies session (guard: web) -- EnsureClienteRegistroCompleto checks cliente.registro_completado_en is set -- VerificarClientePresentaMora checks client has no overdue installments | v[MarketVentaController::store(CrearVentaClienteRequest $request)] |-- CrearVentaClienteRequest validates: items, cuotas, beneficiario, etc. | v[VentaService::crearVenta()] |-- Creates OrdenCompra (estado: PENDIENTE) |-- Creates Venta + VentaDetalle records (one Venta per Empresa) |-- Calls registrarEnCerticamara() to create the digital pagaré (stores certicamara_uuid on Cliente) |-- Decrements Precio.inventario for each line item |-- Generates Cuota schedule via generarCuotas() / generarCuotasPorOrden() | |-- Returns Inertia redirect to the order detail page | v[Browser receives redirect, Inertia navigates to /mis-compras/{id}]
Phase 2 — Pagaré webhook (asynchronous, queued)Certicámara POST /api/v1/webhooks/certicamara (after the customer signs) | v[CerticamaraController::__invoke()] |-- Looks up Cliente by certicamara_uuid |-- Dispatches ValidarPagareDigital($cliente, $payload) onto the 'creditos' queue | v[ValidarPagareDigital::handle()] |-- If state === 'signed': sets cliente.pagare_firmado_en and dispatches | ProcesarPagareDigital for each PENDIENTE OrdenCompra of the cliente |-- If state in ('blocked', 'expired'): rejects every PENDIENTE order/venta, | restocks inventory, clears certicamara_uuid, sets puede_intentar_firmar_pagare_en | v[ProcesarPagareDigital::handle()] |-- Loads ventas with sucursal.empresa and user.cliente eager-loaded |-- Regenerates Cuota rows via VentaService::generarCuotas() / generarCuotasPorOrden() | (no idempotency guard — known risk of duplicate cuotas) |-- Dispatches GenerarCreditoDeVenta(empresa, cliente, venta) per Venta onto 'creditos' | v[GenerarCreditoDeVenta::handle() — tries=3, timeout=900s, backoff=60s] |-- Validates cupo_disponible >= venta.total |-- Calls CreditoService::crearClienteEnCredito() then ::generarCredito() (SOAP -> Core Crédito / SHIVAM) |-- Success: OrdenCompra -> PROCESADA, Venta -> APROBADA, decrement cliente.cupo_disponible |-- Explicit rejection from credit service: rechazarVenta() restocks inventory, Venta -> RECHAZADA |-- Unhandled exception after 3 retries: job is marked failed in the failed_jobs table | but no failed() handler exists, so Venta stays PENDIENTE and inventory is not restoredLos eventos VentaCompletada / VentaCancelada bajo app/Events/Facturacion/ son código muerto — nunca son despachados desde controladores, servicios o jobs.
4.3 Flujo de Aprobación de Crédito (Multi-paso)
GET /usuario/cupo/legal-check |-- [CheckIntentosLimiteDiarios middleware] |-- AprobarCupoController::legalCheck() | |-- LegalCheckService (calls TransUnion API via Http::transunion() macro) | |-- Creates AprobarCupoEvento (event sourcing record) | |-- Returns LegalCheckResult DTO |GET /usuario/cupo/identity-validation-generate-otp |-- AprobarCupoController::identityValidationGenerateOTPCode() | |-- IdentityValidationService (calls Experian CrossCore API) | |-- Returns GenerateOTPResult DTO |POST /usuario/cupo/identity-validation-verify-otp |-- AprobarCupoController::identityValidationVerifyOTPCode() | |-- IdentityValidationService | |-- Returns VerifyOTPResult DTO |GET /usuario/cupo/hdc-validation |-- AprobarCupoController::hdcValidation() | |-- HDCValidationService (calls DataCredito API) | |-- Records event in aprobar_cupo_eventos |GET /usuario/cupo/aprobar |-- AprobarCupoController::aprobarCupo() | |-- AprobarCupoService (final decision) | |-- Updates cliente.cupo_asignado / cupo_disponible / cupo_vence_en4.4 Flujo de Request del Aliado
Browser GET /aliados/ventas/listado (with session cookie, guard: app) | v[Middleware: auth:app] -- Authenticate checks 'app' guard | v[HandleInertiaRequests::share()] -- Detects user belongs to an Empresa via empresa_user pivot -- Shares empresa data + lineas instead of cliente/carrito data | v[Aliado\VentaController::render()] |-- VentaService queries ventas scoped to user's empresa/sucursal |-- Returns Inertia::render('ally/ventas/Index', [...]) | v[Vue Frontend] Inertia resolves --> pages/ally/ventas/Index.vue Layout wrapper --> AllyLayout.vue5. Patrones Arquitectónicos Clave
5.1 Patrón Service-DTO
Los controladores nunca interactúan directamente con los Models para operaciones de escritura. El flujo es siempre:
- El Controller recibe la entrada validada (vía Form Request o validación inline)
- El Controller crea un DTO (clase PHP plana) a partir de los datos validados
- El Controller pasa el DTO a un Service
- El Service realiza la lógica de negocio y persiste vía Models
- Los DTOs siguen la convención de nombres
Crear*DTO(crear) yActualizar*DTO(actualizar), con DTOs*Resultpara respuestas de lectura/externas
5.2 Autenticación Multi-Guard
Dos guards de autenticación separados operan en paralelo:
- Guard
web: Sesiones del cliente (auth estándar de Laravel) - Guard
app: Sesiones del aliado (flujo de login separado en/aliados/ingresar)
Ambos guards comparten la misma tabla users. La autorización es manejada principalmente por la columna string users.rol y checks inline; el paquete Spatie Permission y sus alias de middleware role / permission / role_or_permission están instalados y registrados pero el código activo actualmente no los usa como puerta de control principal.
5.3 Monolito Inertia.js (SPA Dirigida por el Servidor)
La aplicación es un monolito — no hay una SPA separada dirigida por API. Los controladores de Laravel devuelven respuestas Inertia::render() que se resuelven a componentes de página Vue. El middleware HandleInertiaRequests comparte props globales (estado de auth, datos de navegación, carrito, configuración de crédito) en cada request. Ziggy proporciona rutas con nombre de Laravel al frontend vía route().
5.4 Efectos Secundarios Dirigidos por Eventos (Parcial)
Los eventos de dominio se usan selectivamente; la mayor parte del pipeline de facturación corre a través de jobs, no eventos.
- Eventos de Postulación (activos):
PostulacionDeAliadoCreada,PostulacionDeAliadoAprobada,PostulacionDeAliadoRechazadaODescartadason despachados desdePostulacionServicey disparan listeners que envían correos de notificación síncronos a administradores y postulantes. - Logging de validación (activo):
ValidationLoges despachado desde los servicios de aprobación de crédito en las transiciones START / FINISH_SUCCESS / FINISH_UNSUCCESS;StoreValidationLogescribe la fila enaprobar_cupo_eventos. - Eventos de facturación (código muerto):
VentaCompletadayVentaCanceladabajoapp/Events/Facturacion/vienen conVentaCompletadaListener/VentaCanceladaListenerpero ninguno de los eventos es despachado en ninguna parte del código. El pipeline real de venta-a-crédito es la cadena de webhook de Certicámara (ValidarPagareDigital→ProcesarPagareDigital→GenerarCreditoDeVenta), no una cadena de eventos.
5.5 Integración con APIs Externas vía Macros HTTP
El AppServiceProvider registra cinco macros de cliente HTTP (transunion, expirianCrossCoreAuth, expirianCrossCore, datacredito, certicamara) que pre-configuran URLs base, headers de autenticación y timeouts. Los servicios consumen estas macros en lugar de construir clientes HTTP inline.
5.6 Event Sourcing para Aprobación de Crédito
La tabla aprobar_cupo_eventos con claves primarias UUID registra cada paso del proceso de aprobación de crédito como eventos inmutables (enums ProcessType + EventType + payload JSON contexto). Esto proporciona una pista de auditoría completa del legal check, validación de identidad (OTP, preguntas), validación HDC, y aprobación final.
5.7 Estrategia de Caché
HandleInertiaRequests usa Cache::remember() para datos globales accedidos frecuentemente:
- Categorías de productos (
lineas) - Marcas (
brands_allied) - Categorías populares (
popular_categories) - Estadísticas por usuario (
user_stats_{id}) - Carrito por usuario (
carrito_{id})
Todo cacheado con un TTL configurable (app.cache_global_ttl, por defecto 3600s).
5.8 Procesamiento en Segundo Plano Encolado
La conexión de cola es database (config/queue.php). El trabajo de larga duración o diferible se despacha a la cola:
ValidarPagareDigital— despachado desde el webhook de Certicámara; decide si el pagaré está firmado y o bien rechaza todas las órdenes PENDIENTE o reparte unProcesarPagareDigitalpor orden (cola:creditos).ProcesarPagareDigital— regenera las cuotas para la orden y despacha unGenerarCreditoDeVentapor Venta (cola:creditos).GenerarCreditoDeVenta— llama a Core Crédito / SHIVAM víaCreditoService, marca la Venta como APROBADA, decrementacupo_disponible. Tries=3, timeout=900s, backoff=60s. Sin handlerfailed()— las excepciones no manejadas tras 3 reintentos dejan la Venta en PENDIENTE (cola:creditos).ProcesarOrdenesAbandonadas— programado cada 15 minutos víaroutes/console.php; no implementaShouldQueue, por lo que corre síncronamente en el proceso del scheduler.IniciarCargaMasivaProducto— punto de entrada de importación masiva de productos (cola:procesar_documento_productos).ProcesarFilaProducto— un job por cada fila de la hoja de cálculo (cola:procesar_fila_producto).
Los nombres de las colas se asignan en el sitio de despacho vía ->onQueue(...), no en la clase del job.
5.9 Modelo Base con Timestamps en Español
Una clase base Modelo custom sobrescribe los created_at/updated_at por defecto de Laravel con creado_en/actualizado_en para la mayoría de modelos de dominio. Las excepciones (User, Cuota, BackendRequestLog) usan los timestamps estándar de Laravel.
5.10 Soporte SSR
La configuración de Vite incluye un entry SSR (resources/js/ssr.ts), indicando que la aplicación soporta el renderizado del lado del servidor de páginas Vue para mejorar el rendimiento de carga inicial y SEO.