11 - Diagrama de Flujo de Datos
Validated Corrections
- El flujo de compra documentado aqui necesita matiz importante:
POST /mis-comprasno pasa porcliente_registro_completoniverificar_cliente_presenta_mora; esas barreras estan enGET /checkout. user_statsno esta personalizado por usuario en el codigo observable: la clave de cache esuser_stats_{user_id}(ouser_stats_guest), pero el cuerpo viene deUser::stats(15, $user)(app/Models/User.php:178), y dentro de ese metodo el parametro$userno se utiliza — las 4 consultas (ofertas_del_dia,encantar,mas_vendidos,intereses) devuelven el mismo contenido para todos los usuarios. La key por usuario solo genera caches duplicados del mismo payload.- El contrato frontend-backend de wishlist tiene un bug real:
useWishlist.ts:29haceArray.isArray(data), peroListaDeseoController::indexretornaresponse()->json($this->listaDeseoService->obtenerListaDeseos(...))yobtenerListaDeseos()retornaLengthAwarePaginator(paginate($perPage)), un objeto — no un array. ElArray.isArraysiempre es false en produccion y la lista de deseos se renderiza vacia. Profile.vueataform.namey muestrauser.name, pero el modeloUserno tiene columnaname; las columnas reales sonnombresyapellidos. La pagina de perfil esta desincronizada del modelo.ally/ventas/Page.vue:88matchea sobrecase 'cancelada', peroEstadoVentano expone ese valor: el enum tieneRECHAZADAyABANDONADAcomo estados de cancelacion. El casocanceladanunca activa el branch.- En entorno
local, el flujo mock de DataCredito no llama al endpoint API de esta misma app, sino ahttps://test.miplante.com/api/v1/datacredito/historial— un host externo compartido, no un fixture local. - Este documento sigue siendo util a nivel macro, pero no debe usarse como fuente exacta de contratos JSON sin contrastarlo con controladores y composables.
1. Vision General del Flujo de Datos de Alto Nivel
graph LR
subgraph "Capa Cliente"
BROWSER["Navegador (Vue 3 SPA)"]
end
subgraph "Transporte"
INERTIA["Puente Inertia.js"]
AXIOS["Axios (XHR/JSON)"]
end
subgraph "Aplicacion Laravel"
MW["Stack de Middleware<br/>HandleInertiaRequests<br/>Auth guards (web/app)<br/>ConsultarCupo / RegistroCompleto / Mora"]
CTRL["Controladores<br/>Market / Aliado / Auth<br/>AprobarCliente / Webhooks / API"]
SVC["Capa de Servicios (29 servicios)<br/>+ DTOs (39 clases)"]
EVENTS["Eventos y Listeners<br/>(sincronos, bloqueantes)"]
QUEUE["Cola (Redis/DB)"]
JOBS["Jobs<br/>ValidarPagare / ProcesarPagare<br/>GenerarCredito / CargaMasiva<br/>OrdenesAbandonadas"]
end
subgraph "Almacenes de Datos"
DB[(MySQL/SQLite<br/>38 tablas)]
CACHE[(Cache<br/>Redis/File<br/>lineas, carrito,<br/>user_stats, tokens)]
end
subgraph "APIs Externas"
TU["TransUnion<br/>Legal Check"]
EXP["Experian CrossCore<br/>Identidad + OTP"]
DC["DataCredito<br/>HDC Plus"]
CERT["Certicamara<br/>ePagare"]
CORE["Core Credito<br/>SHIVAM Banking"]
EMCALI["EMCALI<br/>Membresias"]
end
subgraph "Webhooks Entrantes"
WH_CERT["POST /api/v1/webhooks/certicamara"]
end
%% Navegador a Laravel
BROWSER -->|"Visitas Inertia<br/>(navegacion de pagina completa)"| INERTIA
BROWSER -->|"llamadas axios<br/>(API JSON)"| AXIOS
INERTIA --> MW
AXIOS --> MW
MW --> CTRL
%% Controlador a servicios y datos
CTRL --> SVC
SVC --> DB
SVC --> CACHE
MW -->|"props compartidas<br/>(auth, carrito, lineas)"| CACHE
%% Respuesta de vuelta al navegador
CTRL -->|"Inertia::render() o<br/>response()->json()"| BROWSER
%% Eventos y jobs
SVC -->|"event()"| EVENTS
EVENTS -->|"listeners sincronos"| DB
SVC -->|"dispatch()"| QUEUE
CTRL -->|"dispatch()"| QUEUE
QUEUE --> JOBS
JOBS --> SVC
%% Llamadas a APIs externas (salientes)
SVC -->|"Macros Http"| TU
SVC -->|"OAuth + API"| EXP
SVC -->|"OAuth + API"| DC
SVC -->|"Token API"| CERT
SVC -->|"SOAP/XML"| CORE
SVC -->|"HTTP GET"| EMCALI
%% Webhook entrante
CERT -->|"callback de firma"| WH_CERT
WH_CERT --> CTRL
CTRL -->|"dispatch job"| QUEUE
%% Estilos
style BROWSER fill:#4A90D9,stroke:#333,color:#fff
style DB fill:#2ECC71,stroke:#333,color:#fff
style CACHE fill:#F39C12,stroke:#333,color:#fff
style QUEUE fill:#E74C3C,stroke:#333,color:#fff
2. Flujos de Datos Detallados por Escenario
2a. Navegacion de Productos (Flujo de Lectura)
sequenceDiagram
participant B as Navegador
participant I as Inertia.js
participant MW as HandleInertiaRequests
participant PC as ProductoController
participant DB as MySQL
participant C as Cache
B->>I: GET /productos/{producto} (clic en enlace de producto)
I->>MW: Solicitud Inertia (primera visita = HTML completo, siguientes = XHR JSON)
Note over MW: Ensamblaje de props compartidas (cada solicitud)
MW->>C: Cache::remember('lineas', 3600s)
C-->>MW: lineas (arbol de categorias)
MW->>C: Cache::remember('brands_allied', 3600s)
C-->>MW: marcas (todos los registros de Marca)
MW->>C: Cache::remember('popular_categories', 3600s)
C-->>MW: categorias populares (top 8)
alt Usuario autenticado (cliente)
MW->>C: Cache::remember('carrito_{user_id}', 3600s)
C-->>MW: items del carrito con precios, productos, lineas
MW->>C: Cache::remember('user_stats_{user_id}', 3600s)
C-->>MW: estadisticas del usuario
MW->>DB: user->cliente (relacion)
DB-->>MW: clienteData (cupo, estado de registro)
else Usuario autenticado (aliado)
MW->>DB: user->empresas()->with('lineas')
DB-->>MW: informacion de empresa
end
MW->>PC: Reenvia al controlador (props compartidas adjuntas)
Note over PC: Route model binding resuelve Producto $producto directamente<br/>Sin ProductoService::show() — el controlador llama $producto->load() inline
PC->>DB: $producto->load(['empresa', 'linea', 'marca', 'precios.imagenes'])
DB-->>PC: Producto con relaciones eager-loaded
PC->>I: Inertia::render('product/Detail', {product: ...})
I->>B: JSON parcial (producto + props compartidas) o pagina HTML completa
Note over B: Vue resuelve pages/product/Detail.vue<br/>Envuelto en MarketLayout.vue<br/>Renderiza el producto con lineas, carrito, auth compartidos
2b. Checkout del Carrito y Creacion de Venta (Flujo de Escritura)
sequenceDiagram
participant B as Navegador
participant AX as Axios (XHR)
participant MW as Middleware
participant VC as Market\VentaController
participant VS as VentaService
participant CS as CerticamaraService
participant DB as MySQL
participant Q as Cola (creditos)
participant J as GenerarCreditoDeVenta
Note over B: Usuario en Checkout/Index.vue<br/>Sincroniza cantidades del carrito via axios PUT/POST/DELETE
B->>AX: POST /mis-compras (payload JSON)
Note over AX: payload = {user_id, numero_cuotas,<br/>subtotal, total, detalles[],<br/>beneficiario{}, causal}
AX->>MW: auth (web guard)
Note over MW: Valida que el usuario este autenticado.<br/>Nota: cliente_registro_completo y verificar_mora<br/>solo aplican a GET /checkout, no a POST /mis-compras.
MW->>VC: store(CrearVentaClienteRequest)
Note over VC: El Form Request valida:<br/>items, cuotas, beneficiario,<br/>subtotal, total, detalles
VC->>VC: Verifica comprasPendientes (rechaza si existe)
VC->>VC: Verifica puede_intentar_firmar_pagare_en (rechaza si es futuro)
VC->>VS: crearVenta(CrearVentaDTO)
Note over VS: Inicia DB::transaction
VS->>DB: Carga User + Cliente
DB-->>VS: user, cliente (cupo_disponible)
VS->>VS: Valida cupo_disponible >= subtotal
VS->>DB: Carga registros Precio con producto.empresa.sucursales
DB-->>VS: precios con relaciones
VS->>VS: Valida inventario (cantidad <= inventario)
VS->>DB: CREATE OrdenCompra (estado: PENDIENTE)
DB-->>VS: ordenCompra
VS->>CS: crearPagare({datos del cliente, tasa interes...})
CS->>CS: POST a la API Certicamara ePagare
CS-->>VS: respuesta {uuid}
VS->>DB: UPDATE cliente SET certicamara_uuid
VS->>DB: CREATE Beneficiario
loop Por cada grupo de empresa
VS->>DB: CREATE Venta (vinculada a OrdenCompra)
VS->>DB: CREATE VentaDetalle[] (por item)
VS->>DB: UPDATE Precio SET inventario -= cantidad
end
VS->>DB: CREATE Cuota[] (cronograma de cuotas)
alt pagare ya firmado (pagare_firmado_en != null)
VS->>Q: dispatch(GenerarCreditoDeVenta)
Q->>J: Procesa en la cola creditos
J->>J: Registra compra en Core Credito (SHIVAM)
end
Note over VS: Commit de DB::transaction
VS-->>VC: Venta(s) creadas
VC->>DB: DELETE user.carrito()
VC->>DB: Cache::forget('carrito_{user_id}')
VC-->>B: JSON {success: true, data: venta}
Note over B: El frontend muestra mensaje de exito
2c. Flujo de Aprobacion de Credito (Multi-Paso con APIs Externas)
sequenceDiagram
participant B as Navegador (ModalValidate.vue)
participant AX as Axios
participant MW as Middleware
participant AC as AprobarCupoController
participant LCS as LegalCheckService
participant IVS as IdentityValidationService
participant HDCVS as HDCValidationService
participant ACS as AprobarCupoService
participant ECS as ExtenderCupoService
participant TU as TransUnion API
participant EXP as Experian CrossCore
participant DC as DataCredito API
participant EMCALI as EMCALI API
participant DB as MySQL
participant C as Cache
Note over B: Paso 1: Legal Check
B->>AX: GET /usuario/cupo/legal-check
AX->>MW: auth + CheckIntentosLimiteDiarios
MW->>AC: legalCheck()
AC->>LCS: manejar(user)
LCS->>TU: POST /consulta (Basic Auth, dni, tipoId)
TU-->>LCS: {nombre, estadoDocumento, data[hallazgos en listas]}
LCS->>LCS: Valida similitud de nombre >= 70%
LCS->>LCS: Verifica estado del documento "VIGENTE"
LCS->>LCS: Verifica listas restringidas
LCS-->>AC: DTO LegalCheckResult
AC->>AC: event(ValidationLog) FINISH_SUCCESS/UNSUCCESS
Note over AC: El listener StoreValidationLog escribe en aprobar_cupo_eventos (sincrono)
AC-->>B: JSON {success, message}
Note over B: Paso 2: Validacion de Identidad
B->>AX: GET /usuario/cupo/identity-validation
AX->>AC: identityValidation()
AC->>IVS: validarIdentidad(user)
IVS->>EXP: POST /token (Okta OAuth)
EXP-->>IVS: access_token
IVS->>EXP: POST /identificacion/validar
EXP-->>IVS: resultado de validacion
IVS->>C: Cachea estado de validacion (60 min)
IVS-->>AC: DTO IdentityValidationResult
AC->>AC: event(ValidationLog)
AC-->>B: JSON {success}
Note over B: Paso 3: Generacion + Verificacion de OTP
B->>AX: GET /usuario/cupo/identity-validation-generate-otp
AX->>AC: identityValidationGenerateOTPCode()
AC->>IVS: generarOTP(user)
IVS->>EXP: POST /otp/initialize + POST /otp/evaluation
EXP-->>IVS: OTP enviado a correo/telefono
IVS->>C: Cachea transaccion_otp_id (30 min)
AC-->>B: JSON {data: {correo: bool, telefono: bool}}
B->>B: El usuario ingresa el codigo OTP
B->>AX: POST /usuario/cupo/identity-validation-verify-otp {otp_code}
AX->>AC: identityValidationVerifyOTPCode(request)
AC->>IVS: verificarOTP(user, code)
IVS->>EXP: POST /otp/verify (codigo con hash SHA-256)
EXP-->>IVS: {valido, requiere_cuestionario}
AC-->>B: JSON {data: {valido, requiere_cuestionario}}
alt Cuestionario requerido
Note over B: Paso 3b: Preguntas de Conocimiento
B->>AX: GET /usuario/cupo/identity-validation-generate-questions
AX->>AC: identityValidationGenerateQuestions()
AC->>IVS: generarCuestionario(user)
IVS->>EXP: POST /identificacion/preguntas
EXP-->>IVS: arreglo de preguntas
AC-->>B: JSON {data: preguntas[]}
B->>B: El usuario responde las preguntas
B->>AX: POST /usuario/cupo/identity-validation-verify-questions
AX->>AC: identityValidationVerifyQuestions(request)
AC->>IVS: verificarCuestionario(user, respuestas[])
IVS->>EXP: POST /identificacion/verificar
EXP-->>IVS: resultado del score
AC-->>B: JSON {success}
end
Note over B: Paso 4: Validacion HDC (Historial Crediticio)
B->>AX: GET /usuario/cupo/hdc-validation
AX->>AC: hdcValidation()
AC->>AC: event(ValidationLog) START
AC->>HDCVS: manejar()
HDCVS->>DC: POST /token (OAuth, cacheado 590s)
DC-->>HDCVS: Token Bearer
HDCVS->>DC: POST /hdcplus (consulta de historial crediticio)
DC-->>HDCVS: Reporte HDC Plus + score
Note over HDCVS: Respuesta cacheada 24h
AC->>AC: event(ValidationLog) FINISH
AC-->>B: JSON {success}
Note over B: Paso 5: Aprobacion Final + Extension
B->>AX: GET /usuario/cupo/aprobar
AX->>AC: aprobarCupo()
AC->>ACS: validarSiElClienteTieneSuCupoAprobado(id)
Note over ACS: Consulta: por ProcessType, el evento MAS RECIENTE<br/>de este mes calendario debe ser FINISH_SUCCESS.<br/>Devuelve true cuando >= 5 process_types pasan.<br/>NO es una secuencia estricta de 7 pasos — el orden no importa.
ACS->>DB: SELECT tipo_proceso ... GROUP BY tipo_proceso<br/>HAVING MAX(CASE WHEN evento=FINISH_SUCCESS THEN creado_en END)<br/>= MAX(creado_en)
ACS-->>AC: true si count(process_types que pasan) >= 5
AC->>DB: UPDATE cliente SET cupo_vence_en<br/>(now()->addMonth()->startOfMonth()->addDays(5)->endOfDay())
AC->>ECS: extender(cliente)
ECS->>EMCALI: GET validacion de membresia/contrato
EMCALI-->>ECS: cupos aprobados
ECS->>DC: Consulta de score crediticio (cacheado 24h)
DC-->>ECS: score
ECS->>ECS: Mapea score -> %: 0-350=0%, 351-470=25%,<br/>471-670=50%, 671-815=75%, 816-1000=100%, null=25%
ECS->>ECS: incremento = (cupo_emcali - cupo_base_estrato) * pct
ECS-->>AC: resultado de extension (extendido: bool)
AC-->>B: JSON {success, data: cliente, extension}
2d. Procesamiento del Webhook de Certicamara (Flujo Asincrono Entrante)
sequenceDiagram
participant CERT as Certicamara ePagare
participant API as POST /api/v1/webhooks/certicamara
participant CC as CerticamaraController
participant Q as Cola (creditos)
participant VPJ as Job ValidarPagareDigital
participant PPJ as Job ProcesarPagareDigital
participant GCJ as Job GenerarCreditoDeVenta
participant VS as VentaService
participant CCS as CoreCreditoService
participant DB as MySQL
Note over CERT: El usuario firma (o bloquea/expira) el pagare en el portal Certicamara
CERT->>API: POST {uuid, state, message, ...}
API->>CC: __invoke(request)
CC->>DB: Cliente::where('certicamara_uuid', uuid)
DB-->>CC: cliente (o null)
alt Cliente no encontrado
CC-->>CERT: 404 {success: false}
else Cliente encontrado
CC->>Q: dispatch(ValidarPagareDigital, cliente, payload)
CC-->>CERT: 200 {success: true}
end
Note over Q: Inicia el procesamiento asincrono
Q->>VPJ: handle()
VPJ->>VPJ: Parsea el estado del payload del webhook
alt state = "signed" (pagare aceptado)
VPJ->>DB: OrdenCompra WHERE user_id AND estado=PENDIENTE
DB-->>VPJ: ordenes pendientes[]
VPJ->>DB: UPDATE cliente SET pagare_firmado_en = now()
loop Por cada OrdenCompra pendiente
VPJ->>Q: dispatch(ProcesarPagareDigital, orden)
end
Q->>PPJ: handle()
PPJ->>DB: Carga ventas con sucursal.empresa, user.cliente
DB-->>PPJ: ventas[]
PPJ->>VS: generarCuotas(venta) o generarCuotasPorOrden()
VS->>DB: CREATE Cuota[] (cronograma de cuotas)
loop Por cada venta
PPJ->>Q: dispatch(GenerarCreditoDeVenta, empresa, cliente, venta)
end
Q->>GCJ: handle()
GCJ->>CCS: crearCliente(datos) [SOAP/XML a SHIVAM]
CCS-->>GCJ: numero VCARD
GCJ->>CCS: registrarCompra(datos) [SOAP/XML a SHIVAM]
CCS-->>GCJ: referencia de transaccion
alt Exito
GCJ->>DB: UPDATE venta SET estado = APROBADA
GCJ->>DB: UPDATE cliente SET cupo_disponible -= total
else Fallo (despues de 3 reintentos)
GCJ->>DB: UPDATE venta SET estado = RECHAZADA
GCJ->>DB: Restaura inventario en registros Precio
end
else state = "blocked" o "expired"
VPJ->>DB: UPDATE cliente SET certicamara_uuid = null
VPJ->>DB: UPDATE cliente SET puede_intentar_firmar_pagare_en = now() + 24h
VPJ->>DB: UPDATE cliente SET pagare_firmado_en = null
VPJ->>DB: OrdenCompra WHERE user_id AND estado=PENDIENTE (con ventas.detalles.precio)
DB-->>VPJ: ordenes con ventas
loop Por cada orden
VPJ->>DB: UPDATE orden SET estado = RECHAZADA/ABANDONADA
loop Por cada venta en la orden
loop Por cada detalle en la venta
VPJ->>DB: Precio.increment('inventario', cantidad)
end
VPJ->>VS: actualizar(venta, {estado: RECHAZADA/ABANDONADA})
VS->>DB: UPDATE venta
end
end
end
3. Props Compartidas de Inertia (HandleInertiaRequests)
El middleware HandleInertiaRequests ensambla e inyecta los siguientes datos en cada respuesta de Inertia. Estos datos estan disponibles para todos los componentes de pagina Vue via usePage().props.
| Clave Prop | Origen | Cacheada | Descripcion |
|---|---|---|---|
name | config('app.name') | No | Nombre de la aplicacion |
quote | Inspiring::quotes()->random() | No | Cita inspiracional aleatoria (mensaje + autor) |
ziggy | new Ziggy() | No | Todas las rutas nombradas de Laravel + URL actual (para helper route() en JS) |
is_logged | $request->user() !== null | No | Flag booleano de autenticacion |
is_register_view | $request->routeIs('register') | No | Flag booleano para la pagina de registro |
sidebarOpen | Cookie sidebar_state | No | Estado abierto/cerrado de la sidebar (persistido en cookie) |
links | config('app.*') | No | URLs externas: FAQ, Acerca de, SIC, Terminos, Privacidad |
resend_wait_seconds | config('app.resend_wait_seconds') | No | Cooldown para reenvio de correo (por defecto 60s) |
max_search_results | config('app.max_search_results') | No | Limite de paginacion de busqueda (por defecto 10) |
credito | config('app.*') | No | Parametros de simulacion de credito: dias_desfase, tasa_nominal, seguro_vida, porcentaje_fianza, monto_estudio_credito |
lineas | LineaService::getLineas(true) | Si (3600s) | Arbol de categorias de productos (todas las lineas activas) |
brands_allied | Marca::all() | Si (3600s) | Todos los registros de marca |
popular_categories | LineaService::getPopularCategories(8) | Si (3600s) | Top 8 categorias populares de productos |
auth.user | $request->user() | No | Modelo User autenticado completo (o null) |
auth.role | $user->rol() | No | String del rol del usuario |
Props especificas del cliente (cuando el usuario no tiene empresa)
| Clave Prop | Origen | Cacheada | Descripcion |
|---|---|---|---|
auth.clienteData | $user->cliente | No | Modelo Cliente: cupo_asignado, cupo_disponible, cupo_vence_en, registro_completado_en, certicamara_uuid, etc. |
auth.carritoData | Items del carrito del usuario | Si (3600s, key: carrito_{id}) | Arreglo de items del carrito con: precio_id, precio, producto_id, nombre_producto, URL de imagen, cantidad, carrito_id, metadata de linea (id, nombre, plazo_minimo, plazo_maximo) |
user_stats | User::stats(15, $user) | Si (3600s, key: user_stats_{id} o user_stats_guest) | Cuatro listas de productos: ofertas_del_dia, encantar, mas_vendidos, intereses (top 15 cada una). A pesar de la cache key por usuario, el argumento $user no se utiliza dentro de User::stats() — cada usuario recibe el mismo payload. |
Props especificas del aliado (cuando el usuario pertenece a una empresa)
| Clave Prop | Origen | Cacheada | Descripcion |
|---|---|---|---|
empresa | user->empresas()->with('lineas') o user->sucursales->empresa | No | Modelo Empresa completo con lineas (reemplaza clienteData/carritoData) |
Nota clave de diseno: El middleware ramifica segun si el usuario pertenece a una Empresa (aliado) o no (cliente). Los usuarios aliados reciben datos de empresa en lugar de datos de carrito/cliente. Los usuarios invitados reciben null para todas las props relacionadas con auth.
4. Patrones de Comunicacion Frontend-Backend
4.1 Resumen de Patrones
La aplicacion usa tres patrones distintos de comunicacion entre frontend y backend:
| Patron | Transporte | Formato de Respuesta | Usado Para |
|---|---|---|---|
| Visitas de pagina Inertia | router.visit() / router.get() / <Link> | Intercambio completo del componente de pagina (HTML o JSON parcial) | Navegacion de paginas, navegar productos, ver listas |
| Envios de formularios Inertia | router.post() / useForm().submit() | Redireccion de pagina o errores de validacion | Autenticacion (login, registro), reset de contrasena, actualizacion de configuracion, logout |
| Llamadas JSON con Axios | axios.get/post/put/delete() | JSON {success, data, message} | CRUD de carrito, checkout, pasos de aprobacion de credito, wishlist, operaciones CRUD de aliado, datos de dashboard |
4.2 Visitas de Pagina Inertia (Navegacion)
Usadas para transiciones de paginas de solo lectura donde cambia el componente de pagina completo.
router.visit(route('checkout.view')) -- Navega a la pagina de checkoutrouter.visit(route('home')) -- Navega a home/storefrontrouter.visit(route('aliado.productos.render')) -- Navega a la pagina de productos del aliadorouter.get(route('cliente.compras.index')) -- Navega a la lista de comprasrouter.get(route('cliente.compras.show', id)) -- Navega al detalle de compraEl adaptador Inertia intercepta estos y envia una solicitud XHR con header X-Inertia. Laravel devuelve el nombre del componente de pagina + props como JSON (o HTML completo en la primera visita). El frontend intercambia el componente de pagina sin recarga completa.
4.3 Envios de Formularios Inertia (Flujos de Auth)
Usados para envios de formularios que esperan un redirect o manejo estandar de errores de validacion de Laravel.
router.post(route('login'), {email, password}) -- Login del clienterouter.post(route('register'), {name, email, ...}) -- Registro del clienterouter.post(route('aliado.login'), {email, password}) -- Login del aliadorouter.post('/logout') -- Logout (ambos guards)router.post(route('verification.send')) -- Reenvio de correo de verificacionrouter.post(route('password.email'), {email}) -- Solicitud de reset de contrasenaEstos usan el composable useForm() de @inertiajs/vue3 para estado reactivo de formulario, manejo automatico de CSRF, y gestion integrada de estados processing/errors.
4.4 Llamadas API JSON con Axios (Operaciones de Datos)
Usadas para operaciones CRUD y flujos multi-paso donde la pagina no cambia.
Configuracion (de app.ts):
- Timeout: 300,000ms (5 minutos — acomoda llamadas lentas a APIs externas)
- CSRF:
withXSRFToken: true+withCredentials: true - Header:
X-Requested-With: XMLHttpRequest
Operaciones del carrito:
axios.post(route('carrito.store'), {precio_id, cantidad}) -- Agregar al carritoaxios.put(route('carrito.update', {carrito: id}), {precio_id, cantidad}) -- Actualizar cantidadaxios.delete(route('carrito.destroy', {carrito: id})) -- Remover itemCheckout/compra:
axios.post(route('cliente.compras.store'), {detalles[], beneficiario, ...}) -- Crear ventaaxios.get(route('cliente.compras.index'), {params: {per_page}}) -- Listar comprasFlujo de aprobacion de credito (ModalValidate.vue — pasos secuenciales):
axios.get('/usuario/cupo/legal-check') -- Paso 1: Legal checkaxios.get('/usuario/cupo/identity-validation') -- Paso 2: Identidadaxios.get('/usuario/cupo/identity-validation-generate-otp') -- Paso 3a: Generar OTPaxios.post('/usuario/cupo/identity-validation-verify-otp', {otp_code}) -- Paso 3b: Verificar OTPaxios.get('/usuario/cupo/identity-validation-generate-questions') -- Paso 3c: Preguntasaxios.post('/usuario/cupo/identity-validation-verify-questions', {respuestas[]}) -- Paso 3d: Verificaraxios.get('/usuario/cupo/hdc-validation') -- Paso 4: Historial crediticioaxios.get('/usuario/cupo/aprobar') -- Paso 5: Aprobacion finalWishlist:
axios.get(route('lista-deseos.index')) -- Carga wishlist (DEVUELVE PAGINATOR, el frontend asume array)axios.post(route('lista-deseos.store'), {precio_id}) -- Agregar a wishlist (devuelve arreglo de un solo elemento)axios.delete(route('lista-deseos.destroy', {lista_deseo: id})) -- Remover de wishlistDiscrepancia de contrato:
useWishlist.ts:29haceArray.isArray(data) ? data.map(...) : []. El backend (ListaDeseoController::index) devuelveresponse()->json($listaDeseoService->obtenerListaDeseos($search, $perPage))yobtenerListaDeseos()devuelve unLengthAwarePaginator. Eso serializa a un objeto condata: [...], current_page, ..., NO un arreglo de primer nivel. La verificacionisArraydel composable siempre es false; la wishlist se renderiza vacia.
Dashboard/operaciones del aliado:
axios.get(route('aliado.ventas.index'), {params, headers}) -- Listar ventas (paginadas)axios.get(route('aliado.ventas.estadisticas'), {params}) -- Estadisticas de ventasaxios.get(route('aliado.dashboard.resumen'), {params}) -- Resumen del dashboardaxios.get(route('aliado.dashboard.rendimiento'), {params}) -- Metricas de rendimientoaxios.get/post/put/delete(route('aliado.empleados.*')) -- CRUD de empleadosaxios.get/post/delete(route('aliado.marcas.*')) -- CRUD de marcasaxios.get/post(route('aliado.postulaciones.*')) -- Gestion de postulacionesaxios.put(route('aliado.pedidos.update'), {estado}) -- Actualizar estado de pedidoaxios.post(route('aliado.productos.carga-masiva'), formData) -- Carga masiva de productos4.5 Bus de Eventos Global (Solo Frontend)
La aplicacion usa mitt como emisor de eventos liviano disponible en app.config.globalProperties.emitter. Esto habilita la comunicacion entre componentes dentro del SPA Vue sin involucrar al backend (p. ej., notificaciones de actualizacion del carrito, disparadores de modales).
4.6 Arbol de Decision del Flujo de Comunicacion
Es esto una navegacion de pagina? SI -> router.visit() / router.get() / componente <Link> NO -> Es esto un formulario de auth estandar (login, registro, contrasena)? SI -> router.post() con useForm() NO -> Es esto una operacion de datos (CRUD, paso de API, fetch)? SI -> axios.get/post/put/delete() devolviendo JSON5. Resumen del Flujo de Datos por Direccion
5.1 Flujos de Datos Salientes (App -> Externo)
| Flujo | Disparador | Transporte | Destino | Datos Enviados |
|---|---|---|---|---|
| Cribado legal | El usuario inicia aprobacion de credito | HTTP POST (Basic Auth) | TransUnion | Tipo de documento + DNI |
| Validacion de identidad | El usuario continua el flujo de credito | HTTP POST (OAuth 2.0) | Experian CrossCore | Nombres, documento, fecha de expedicion |
| Envio/verificacion de OTP | El usuario ingresa OTP | HTTP POST (OAuth 2.0) | Experian CrossCore | ID de transaccion, OTP con hash |
| Historial crediticio | El usuario continua el flujo de credito | HTTP POST (OAuth 2.0) | DataCredito | Tipo/numero de documento, apellido |
| Crear pagare | Venta creada | HTTP POST (token API) | Certicamara | Detalles del firmante, tasas de interes, plantilla |
| Registrar cliente | Job procesa la venta | SOAP/XML (headers personalizados) | Core Credito SHIVAM | Datos del cliente para el sistema de credito |
| Registrar compra | Job procesa la venta | SOAP/XML (headers personalizados) | Core Credito SHIVAM | Monto de la compra, referencia |
| Verificar membresia | Aprobacion/extension de credito | HTTP GET (sin auth) | EMCALI | Numero de contrato |
5.2 Flujos de Datos Entrantes (Externo -> App)
| Flujo | Origen | Endpoint | Datos Recibidos | Accion Posterior |
|---|---|---|---|---|
| Callback de firma de pagare | Certicamara | POST /api/v1/webhooks/certicamara | {uuid, state, message} | Despacha la cadena de jobs ValidarPagareDigital |
| Proxy DataCredito (solo dev) | Servidor de pruebas interno | GET /api/v1/datacredito/historial | Datos mock de historial crediticio | Devuelve al HDCValidationService |