Saltearse al contenido

14 - Diagrama de Flujo del Ciclo de Vida del Request

Notas de Runtime Antes de Leer los Diagramas

  • Los middlewares del grupo web HandleAppearance, HandleInertiaRequests, y AddLinkHeadersForPreloadedAssets se agregan solo al stack web en bootstrap/app.php:42-46. El grupo api NO los ejecuta — las rutas API no tienen Inertia, no tienen cookie de apariencia, ni precarga de link-headers.
  • Bug en vivo confirmado (bootstrap/app.php:57-58): el callback exceptions->respond() llama a $apiHandler->$method($exception, $request) pero nunca retorna el resultado. El JSON estructurado de ApiExceptionHandler se construye y se descarta; los clientes reciben la respuesta predeterminada del framework. Los diagramas de flujo a continuación muestran la ruta de error intencionada.
  • La cadena de venta-a-crédito no está dirigida por eventos desde un evento de finalización de venta. VentaCompletada y VentaCancelada existen en app/Events/Facturacion/ y tienen listeners, pero ningún código los dispara. La cadena real está dirigida por webhook: CerticamaraControllerValidarPagareDigitalProcesarPagareDigitalGenerarCreditoDeVenta.
  • El VentaCanceladaListener fallaría si se dispara porque hace referencia a un caso inexistente App\Enum\Facturacion\Estado::CANCELADA (app/Listeners/Facturacion/VentaCanceladaListener.php:6,26). Los únicos enums definidos en ese namespace son EstadoVenta, EstadoCuota, y EstadoPlanCredito.
  • Bucle de feedback del Frontend: el interceptor de éxito de axios en resources/js/plugins/errorInterceptor.ts:31-37 llama a errorLogger.logSuccess() para cada respuesta que no sea /api/error-logs. Cada llamada HTTP exitosa desde el navegador produce un request de seguimiento POST /api/error-logs, que ErrorLogController escribe a través del canal de log frontend. Esto duplica el tráfico navegador→servidor e infla la tabla frontend_error_logs con filas que no son errores.

1. Ciclo de Vida del Request Web (Inertia/Vue)

Este diagrama de flujo rastrea un request web completo desde el navegador a través de cada capa de la aplicación y de regreso a la página Vue renderizada.

flowchart TD
    A["El navegador envía request HTTP<br/>(GET /productos/42)"] --> B["public/index.php"]
    B --> B1["definir timestamp LARAVEL_START"]
    B1 --> B2{"¿Modo mantenimiento?<br/>storage/framework/maintenance.php"}
    B2 -- Sí --> B3["Renderizar página de mantenimiento<br/>(503 Service Unavailable)"]
    B2 -- No --> B4["Requerir vendor/autoload.php<br/>(Autoloader de Composer)"]
    B4 --> B5["Requerir bootstrap/app.php<br/>(Application::configure)"]
    B5 --> B6["app->handleRequest(<br/>Request::capture())"]

    B6 --> C["Bootea el Kernel HTTP de Laravel"]
    C --> C1["Bootean los Service Providers<br/>(AppServiceProvider registra<br/>macros HTTP: transunion,<br/>expirianCrossCore, datacredito,<br/>certicamara)"]

    C1 --> D["Stack de Middleware Web Global"]
    D --> D1["1. HandleAppearance<br/>View::share('appearance',<br/>valor de cookie o 'system')"]
    D1 --> D2["2. HandleInertiaRequests<br/>(extiende Inertia\\Middleware)"]
    D2 --> D2a["version() - versionado de assets<br/>para invalidación de caché"]
    D2a --> D2b["share() - ensamblar props compartidas"]
    D2b --> D2c["Cache::remember lineas,<br/>brands_allied,<br/>popular_categories<br/>(TTL: 3600s)"]
    D2c --> D2d{"¿Usuario autenticado?"}
    D2d -- "Sí (cliente)" --> D2e["Cargar clienteData, carritoData<br/>(cacheados por usuario),<br/>user_stats (cacheado por usuario)"]
    D2d -- "Sí (aliado)" --> D2f["Cargar empresa con lineas<br/>(vía pivot empresas o sucursales)"]
    D2d -- "No (invitado)" --> D2g["props auth = null"]
    D2e --> D3
    D2f --> D3
    D2g --> D3
    D3["3. AddLinkHeadersForPreloadedAssets<br/>(hints de push HTTP/2 para Vite)"]

    D3 --> E["Matching de Rutas<br/>(routes/web.php, routes/ally/web.php,<br/>routes/auth.php, routes/settings.php)"]

    E --> F{"¿Middleware a nivel de ruta?"}
    F -- "auth / auth:app" --> F1["Middleware Authenticate<br/>(personalizado, multi-guard)"]
    F1 --> F1a{"¿Sesión válida<br/>para el guard?"}
    F1a -- "No (guard web)" --> F1b["Redirigir a<br/>route('home', login=true)"]
    F1a -- "No (guard app)" --> F1c["Redirigir a<br/>route('login') = /aliados/ingresar"]
    F1a -- "Sí" --> F2

    F -- "guest / guest:app" --> F2
    F -- "Ninguno (público)" --> F2

    F2{"¿Middleware adicional<br/>de ruta?"}
    F2 -- "cliente_registro_completo" --> F2a["EnsureClienteRegistroCompleto<br/>Verifica registro_completado_en,<br/>cupo_vence_en no expirado"]
    F2 -- "verificar_cliente_presenta_mora" --> F2b["VerificarClientePresentaMora<br/>CreditoService::clientePresentaMora<br/>(falla abierto en excepción)"]
    F2 -- "check_intentos_limite_diarios" --> F2c["CheckIntentosLimiteDiarios<br/>Lanza ValidationException si<br/>se excede el límite diario"]
    F2 -- "consultar_cupo_cliente" --> F2d["ConsultarCupoDelCliente<br/>Refresca cupo desde servicio<br/>externo de crédito (no bloqueante)"]
    F2 -- "verified" --> F2e["EnsureEmailIsVerified"]
    F2 -- "signed" --> F2f["ValidateSignature"]
    F2a --> G
    F2b --> G
    F2c --> G
    F2d --> G
    F2e --> G
    F2f --> G
    F2 -- "Ninguno" --> G

    G["Acción del Controlador<br/>(p. ej. ProductoController@show)"]
    G --> G1["Validación de Form Request<br/>(si POST/PUT: CrearVentaClienteRequest,<br/>LoginRequest, etc.)"]
    G1 --> G2["Crear DTO desde datos validados<br/>(p. ej. CrearVentaDTO)"]
    G2 --> G3["Llamar capa de Service<br/>(p. ej. ProductoService,<br/>VentaService, CarritoService)"]
    G3 --> G4["Service consulta/muta<br/>Modelos Eloquent<br/>(MySQL vía Modelo base)"]
    G4 --> G5{"¿Efectos colaterales?"}
    G5 -- "Sí" --> G5a["Disparar Eventos<br/>(VentaCompletada,<br/>VentaCancelada, etc.)"]
    G5a --> G5b["Listeners síncronos se ejecutan<br/>(disparan jobs en cola,<br/>envían notificaciones)"]
    G5 -- "No" --> G6

    G5b --> G6
    G6["El controlador retorna la respuesta"]
    G6 --> G6a{"¿Tipo de respuesta?"}
    G6a -- "Inertia::render()" --> H["Constructor de Respuesta Inertia"]
    G6a -- "response()->json()" --> H2["Respuesta JSON<br/>(para llamadas axios)"]
    G6a -- "redirect()" --> H3["Redirect Inertia<br/>(302 con header X-Inertia)"]

    H --> H1{"¿Primera visita de página?"}
    H1 -- "Sí" --> H1a["Respuesta HTML completa<br/>(template Blade 'app' +<br/>props de página serializadas +<br/>tags de assets de Vite)"]
    H1 -- "No (header X-Inertia)" --> H1b["Respuesta JSON parcial<br/>{component, props, url, version}"]

    H1a --> I["El navegador recibe HTML"]
    H1b --> I2["Adaptador cliente Inertia<br/>intercambia el componente de página"]
    H2 --> I3["La promise de Axios se resuelve<br/>en el componente Vue"]
    H3 --> I4["Inertia sigue el redirect,<br/>obtiene nueva página como JSON parcial"]

    I --> J["Hidratación de la App Vue"]
    I2 --> J
    J --> J1["resolvePageComponent<br/>('./pages/{name}.vue')"]
    J1 --> J2["Aplicar Layout<br/>(MarketLayout / AllyLayout /<br/>AppLayout / AuthLayout)"]
    J2 --> J3["Renderizar página con<br/>props compartidas + props de página"]
    J3 --> J4["El usuario ve la página renderizada"]

2. Ciclo de Vida del Request API

Este diagrama de flujo cubre requests API stateless que no pasan a través del middleware Inertia.

flowchart TD
    A["El Cliente HTTP envía request<br/>(POST /api/v1/webhooks/certicamara<br/>o GET /api/v1/datacredito/historial<br/>o POST /api/error-logs)"] --> B["public/index.php"]
    B --> B1["Bootear aplicación Laravel"]
    B1 --> C["Grupo Middleware API<br/>(stateless, sin sesión)"]
    C --> C1["ThrottleRequests<br/>(rate limiting)"]
    C1 --> C2["SubstituteBindings<br/>(route model binding)"]

    C2 --> D["Matching de Rutas<br/>(routes/api.php)"]

    D --> D1{"¿Qué endpoint?"}

    D1 -- "/api/v1/webhooks/certicamara" --> E1["CerticamaraController@__invoke"]
    E1 --> E1a["Buscar Cliente por<br/>certicamara_uuid del payload"]
    E1a --> E1b{"¿Cliente encontrado?"}
    E1b -- "No" --> E1c["Retornar 404<br/>{success: false}"]
    E1b -- "Sí" --> E1d["dispatch(ValidarPagareDigital)<br/>a cola 'creditos'"]
    E1d --> E1e["Retornar 200<br/>{success: true}"]

    D1 -- "/api/v1/datacredito/historial" --> E2["DataCreditoController"]
    E2 --> E2a["DataCreditoService<br/>consulta historial de crédito"]
    E2a --> E2b["Retornar JSON<br/>{success, data}"]

    D1 -- "/api/error-logs" --> E3["ErrorLogController@store"]
    E3 --> E3a["Log::channel('frontend')<br/>->warning(payload)"]
    E3a --> E3b["Retornar 200<br/>{success: true}"]

    subgraph "Manejo de Excepciones (API)"
        EX["Excepción lanzada durante el request"]
        EX --> EX1{"¿Clase de excepción en<br/>ApiExceptionHandler::$handlers?"}
        EX1 -- "Sí" --> EX2["Invocar método handler específico"]
        EX2 --> EX2a["AuthenticationException -> 401"]
        EX2 --> EX2b["AuthorizationException -> 403"]
        EX2 --> EX2c["ValidationException -> 422"]
        EX2 --> EX2d["NotFoundHttpException -> 404"]
        EX2 --> EX2e["MethodNotAllowedException -> 405"]
        EX2 --> EX2f["QueryException -> 409/500"]
        EX2 --> EX2g["HttpException -> status dinámico"]
        EX1 -- "No" --> EX3["Log al canal database_backend_request<br/>como warning"]
        EX3 --> EX4["Retornar respuesta original"]
        EX2a & EX2b & EX2c & EX2d & EX2e & EX2f & EX2g --> EX5["Retornar JSON<br/>{data: null, success: false,<br/>error: {type, status, message,<br/>timestamp}}"]
    end

Bug de Código: Respuestas de ApiExceptionHandler descartadas. En bootstrap/app.php, el callback exceptions->respond() llama a $apiHandler->$method($exception, $request) pero NO returna el resultado. Esto significa que las respuestas de error JSON estructuradas de ApiExceptionHandler se generan pero se descartan silenciosamente — la respuesta original del framework es retornada en su lugar. El diagrama de flujo de manejo de errores en la Sección 2 muestra el comportamiento intencionado, no el comportamiento real en runtime.

Mapeos de handler adicionales no mostrados: AccessDeniedHttpExceptionhandleAuthenticationException (retorna 401 — posiblemente un bug de código, probablemente debería ser 403), ModelNotFoundExceptionhandleNotFoundException (retorna 404).


3. Ciclo de Vida del Queue Job

Este diagrama de flujo rastrea el procesamiento de jobs en segundo plano desde el dispatch hasta la ejecución.

flowchart TD
    subgraph "Orígenes de Dispatch de Jobs"
        A1["Scheduler (routes/console.php)<br/>ProcesarOrdenesAbandonadas<br/>cada 15 minutos"]
        A2["CÓDIGO MUERTO: eventos VentaCompletada/VentaCancelada<br/>y listeners existen pero<br/>nunca son disparados. VentaCompletadaListener<br/>NO dispara ningún job."]
        A3["Webhook Controller<br/>CerticamaraController dispara:<br/>- ValidarPagareDigital"]
        A4["Cadena de Jobs<br/>ValidarPagareDigital dispara:<br/>- ProcesarPagareDigital<br/>ProcesarPagareDigital dispara:<br/>- GenerarCreditoDeVenta"]
        A5["Controller<br/>IniciarCargaMasivaProducto dispara:<br/>- ProcesarFilaProducto (por fila)"]
    end

    A1 & A2 & A3 & A4 & A5 --> B["Driver de Cola<br/>(tabla de base de datos: jobs)"]

    B --> C["Queue Worker<br/>(php artisan queue:work)"]
    C --> C1["El worker toma el siguiente job<br/>de la cola (FIFO)"]
    C1 --> C2["Deserializar clase del job"]
    C2 --> C3["Ejecutar método handle()"]

    C3 --> D{"¿Tipo de job?"}

    D -- "ProcesarOrdenesAbandonadas" --> D1["Consultar OrdenCompra donde<br/>estado=PENDIENTE y<br/>más antigua que el umbral"]
    D1 --> D1a["Marcar como ABANDONADA<br/>Restaurar inventario en Precio"]

    D -- "ValidarPagareDigital" --> D2["Parsear estado del payload del webhook"]
    D2 --> D2a{"¿state = 'signed'?"}
    D2a -- "Sí" --> D2b["Actualizar cliente.pagare_firmado_en<br/>Disparar ProcesarPagareDigital<br/>por cada OrdenCompra pendiente"]
    D2a -- "No (bloqueado/expirado)" --> D2c["Anular certicamara_uuid<br/>Establecer cooldown 24h<br/>Cancelar órdenes pendientes<br/>Restaurar inventario"]

    D -- "ProcesarPagareDigital" --> D3["Cargar ventas con relaciones<br/>Generar cuotas<br/>Disparar GenerarCreditoDeVenta<br/>por cada venta"]

    D -- "GenerarCreditoDeVenta" --> D4["CoreCreditoService<br/>SOAP/XML a SHIVAM"]
    D4 --> D4a["crearCliente -> número VCARD"]
    D4a --> D4b["registrarCompra -> referencia de transacción"]
    D4b --> D4c{"¿Éxito?"}
    D4c -- "Sí" --> D4d["venta.estado = APROBADA<br/>cliente.cupo_disponible -= total"]
    D4c -- "No (después de retries)" --> D4e["venta.estado = RECHAZADA<br/>Restaurar inventario"]

    D -- "IniciarCargaMasivaProducto /<br/>ProcesarFilaProducto" --> D5["Parsear fila de la hoja de cálculo<br/>Crear/actualizar Producto + Precio"]

    C3 --> E{"¿Resultado del job?"}
    E -- "Éxito" --> E1["Job removido de la cola<br/>Marcar como completado"]
    E -- "Excepción" --> E2{"¿Retries restantes?<br/>(p. ej. GenerarCreditoDeVenta<br/>tiene 3 retries)"}
    E2 -- "Sí" --> E3["Liberar de regreso a la cola<br/>con delay de backoff"]
    E2 -- "No" --> E4["Mover a la tabla failed_jobs<br/>Registrar la falla"]

4. Ciclo de Vida del Webhook (Certicamara ePagare)

Este diagrama de flujo detalla el webhook entrante de Certicamara a través de toda la cadena de procesamiento asíncrono.

flowchart TD
    A["Servicio Certicamara ePagare<br/>El usuario firma/bloquea/expira<br/>el pagaré en el portal externo"] --> B["POST /api/v1/webhooks/certicamara<br/>{uuid, state, message}"]

    B --> C["Grupo Middleware API<br/>(stateless, rate limited,<br/>sin autenticación)"]

    C --> D["CerticamaraController@__invoke"]
    D --> D1["Extraer uuid del payload del request"]
    D1 --> D2["Cliente::where('certicamara_uuid', uuid)"]

    D2 --> D3{"¿Cliente encontrado?"}
    D3 -- "No" --> D4["Retornar 404<br/>{success: false,<br/>message: 'Cliente no encontrado'}"]
    D3 -- "Sí" --> D5["dispatch(ValidarPagareDigital,<br/>cliente, payload)"]
    D5 --> D6["Retornar 200 inmediatamente<br/>{success: true}<br/>(respuesta rápida de webhook)"]

    D6 --> E["Cola: creditos"]
    E --> F["ValidarPagareDigital::handle()"]
    F --> F1["Parsear state del payload del webhook"]

    F1 --> G{"¿Valor de state?"}

    G -- "'signed'" --> H["Flujo de Pagaré Firmado"]
    H --> H1["OrdenCompra::where(user_id,<br/>estado=PENDIENTE)"]
    H1 --> H2["UPDATE cliente SET<br/>pagare_firmado_en = now()"]
    H2 --> H3["Por cada OrdenCompra pendiente:<br/>dispatch(ProcesarPagareDigital)"]

    H3 --> I["ProcesarPagareDigital::handle()"]
    I --> I1["Cargar ventas con<br/>sucursal.empresa, user.cliente"]
    I1 --> I2["Generar cuotas Cuota[]"]
    I2 --> I3["Por cada venta:<br/>dispatch(GenerarCreditoDeVenta)"]

    I3 --> J["GenerarCreditoDeVenta::handle()"]
    J --> J1["CoreCreditoService::crearCliente<br/>(SOAP/XML a SHIVAM)"]
    J1 --> J2["CoreCreditoService::registrarCompra<br/>(SOAP/XML a SHIVAM)"]
    J2 --> J3{"¿Éxito?"}
    J3 -- "Sí" --> J4["venta.estado = APROBADA<br/>cliente.cupo_disponible -= total"]
    J3 -- "No (3 retries agotados)" --> J5["venta.estado = RECHAZADA<br/>Precio.increment('inventario')"]

    G -- "'blocked' / 'expired'" --> K["Flujo de Pagaré Rechazado"]
    K --> K1["cliente.certicamara_uuid = null"]
    K1 --> K2["cliente.puede_intentar_firmar_pagare_en<br/>= now() + 24h"]
    K2 --> K3["cliente.pagare_firmado_en = null"]
    K3 --> K4["Cargar OrdenCompras pendientes<br/>con ventas.detalles.precio"]
    K4 --> K5["Por cada orden + venta + detalle:<br/>Precio.increment('inventario', cantidad)"]
    K5 --> K6["OrdenCompra.estado = RECHAZADA/ABANDONADA<br/>Venta.estado = RECHAZADA/ABANDONADA"]

5. Rutas de Renderizado SSR vs CSR

Este diagrama de flujo muestra cómo la aplicación decide entre renderizado del lado del servidor y renderizado del lado del cliente, y las rutas de ejecución distintas para cada uno.

flowchart TD
    A["Request HTTP Entrante<br/>(ruta web)"] --> B["Laravel procesa el request<br/>a través de middleware + controller"]
    B --> C["El controller retorna<br/>Inertia::render('PageName', props)"]

    C --> D{"¿El servidor SSR está corriendo?<br/>(proceso Node.js vía<br/>php artisan inertia:start-ssr)"}

    D -- "Sí (SSR habilitado)" --> E["Ruta SSR"]
    E --> E1["Inertia envía datos de página<br/>al servidor SSR de Node.js"]
    E1 --> E2["Punto de entrada ssr.ts"]
    E2 --> E3["createServer() con<br/>createInertiaApp()"]
    E3 --> E4["createSSRApp() crea<br/>instancia Vue del lado del servidor"]
    E4 --> E5["Instalar plugins:<br/>Plugin Inertia + ZiggyVue"]
    E5 --> E5a["Nota: SSR excluye<br/>Toast, errorHandling,<br/>emitter mitt, config de axios,<br/>$formatCOP global"]
    E5a --> E6["resolvePageComponent<br/>('./pages/{name}.vue')"]
    E6 --> E7["renderToString()<br/>(@vue/server-renderer)"]
    E7 --> E8["Retornar HTML pre-renderizado<br/>+ tags de head a Laravel"]
    E8 --> E9["Laravel embebe HTML SSR<br/>en el template Blade 'app'"]
    E9 --> F1["El navegador recibe HTML completo<br/>con contenido pre-renderizado"]
    F1 --> F2["Vite carga el bundle app.ts"]
    F2 --> F3["Vue hidrata el DOM existente<br/>(adjunta event listeners,<br/>hace la página interactiva)"]
    F3 --> F4["Comportamiento completo de SPA activo"]

    D -- "No (solo CSR)" --> G["Ruta CSR"]
    G --> G1{"¿Primera visita o<br/>header X-Inertia?"}
    G1 -- "Primera visita<br/>(sin X-Inertia)" --> G2["Respuesta HTML completa:<br/>template Blade 'app' +<br/>div #app vacío +<br/>datos de página serializados en JSON +<br/>tags script/style de Vite"]
    G2 --> G3["El navegador parsea HTML"]
    G3 --> G4["Vite carga app.ts"]
    G4 --> G5["createInertiaApp()"]
    G5 --> G6["createApp() con plugins:<br/>Inertia, ZiggyVue, Toast,<br/>errorHandling, componente Link"]
    G6 --> G7["Configurar globales:<br/>$formatCOP, emitter (mitt),<br/>defaults de axios (timeout 5min,<br/>CSRF, withCredentials)"]
    G7 --> G8["resolvePageComponent<br/>('./pages/{name}.vue')"]
    G8 --> G9["Montar app al elemento #app"]
    G9 --> G10["initializeTheme()<br/>(claro/oscuro desde cookie)"]
    G10 --> F4

    G1 -- "Visita subsecuente<br/>(header X-Inertia)" --> H["Respuesta JSON parcial"]
    H --> H1["{component, props,<br/>url, version}"]
    H1 --> H2["Adaptador cliente Inertia<br/>intercambia componente de página"]
    H2 --> H3["Sin recarga completa de página<br/>navegación SPA"]
    H3 --> F4

    subgraph "Diferencias Clave: SSR vs CSR"
        DIFF1["SSR: Primer paint contentful más rápido,<br/>SEO-friendly, HTML pre-renderizado"]
        DIFF2["CSR: Div vacío hasta que JS carga,<br/>primer paint más lento, set completo de plugins"]
        DIFF3["SSR usa createSSRApp(),<br/>CSR usa createApp()"]
        DIFF4["SSR corre en modo cluster de Node.js,<br/>CSR corre completamente en el navegador"]
        DIFF5["Ambos: Las navegaciones subsecuentes<br/>siempre son del lado del cliente<br/>(intercambio JSON parcial)"]
    end

6. Flujo de Manejo de Errores

Este diagrama de flujo cubre la arquitectura completa de manejo de errores a través de las capas backend y frontend.

flowchart TD
    subgraph "Manejo de Errores del Backend"
        BE["Excepción lanzada<br/>durante procesamiento del request"]
        BE --> BE1["bootstrap/app.php<br/>callback withExceptions()"]
        BE1 --> BE2["exceptions->respond() intercepta<br/>todas las excepciones con handler personalizado"]

        BE2 --> BE3{"¿Clase de excepción en<br/>ApiExceptionHandler::$handlers?"}

        BE3 -- "Sí" --> BE4["Instanciar ApiExceptionHandler<br/>Llamar al método handler mapeado"]
        BE4 --> BE4a["handleAuthenticationException<br/>-> 401 JSON + log"]
        BE4 --> BE4b["handleAuthorizationException<br/>-> 403 JSON + log"]
        BE4 --> BE4c["handleValidationException<br/>-> 422 JSON + validation_errors + log"]
        BE4 --> BE4d["handleNotFoundException<br/>-> 404 JSON + log"]
        BE4 --> BE4e["handleMethodNotAllowedException<br/>-> 405 JSON + allowed_methods + log"]
        BE4 --> BE4f["handleQueryException<br/>-> 409 (FK/duplicado) o 500 + log"]
        BE4 --> BE4g["handleHttpException<br/>-> status dinámico + log"]

        BE4a & BE4b & BE4c & BE4d & BE4e & BE4f & BE4g --> BE_LOG

        BE3 -- "No (tipo no manejado)" --> BE5["Loguear excepción no manejada<br/>al canal database_backend_request"]
        BE5 --> BE6{"¿Solo producción<br/>(NO local/staging)<br/>Y status 500/503/404/403?"}
        BE6 -- "Sí" --> BE7["Inertia::render('ErrorPage',<br/>{status: code})"]
        BE7 --> BE8["Componente Vue ErrorPage<br/>con ErrorLayout"]
        BE6 -- "No (local/staging<br/>u otro status)" --> BE9{"¿Status 419?<br/>(desajuste CSRF)"}
        BE9 -- "Sí + expectsJson" --> BE10["JSON: 'CSRF token mismatch.<br/>Por favor recarga la pagina<br/>e intenta de nuevo.'"]
        BE9 -- "Sí + web" --> BE11["back() con mensaje flash<br/>'The page has expired'"]
        BE9 -- "No" --> BE12["Retornar respuesta<br/>original del framework"]

        BE_LOG["Log::channel('database_backend_request')<br/>->warning(message, context)"]
        BE_LOG --> BE_LOG1["BackendRequestLogger escribe a<br/>la tabla backend_request_logs"]
        BE_LOG1 --> BE_LOG2["El contexto incluye: clase de excepción,<br/>mensaje, archivo, línea,<br/>URL/método/IP/UA/headers/body del request,<br/>ID/email del usuario"]
    end

    subgraph "Manejo de Errores del Frontend"
        FE["plugin errorHandling<br/>(instalado en app.ts)"]
        FE --> FE1["setupErrorHandling(app)"]

        FE1 --> FE2["1. setupGlobalAxiosInterceptors()"]
        FE2 --> FE2a["Interceptor de request:<br/>Adjuntar trackingId<br/>(omitir /api/error-logs)"]
        FE2a --> FE2b["Interceptor de respuesta exitosa:<br/>errorLogger.logSuccess()<br/>(omitir /api/error-logs)"]
        FE2a --> FE2c["Interceptor de respuesta de error:<br/>errorLogger.logAxiosError()<br/>(omitir /api/error-logs)"]

        FE1 --> FE3["2. app.config.errorHandler"]
        FE3 --> FE3a["Captura errores de componente Vue<br/>errorLogger.logVueError(error,<br/>componentName)"]

        FE1 --> FE4["3. window 'unhandledrejection'"]
        FE4 --> FE4a["Captura rechazos de Promise no manejados<br/>errorLogger.logUnhandledError()"]

        FE1 --> FE5["4. window 'error'"]
        FE5 --> FE5a["Captura errores globales de JS<br/>errorLogger.logUnhandledError(<br/>message, filename, lineno, colno)"]

        FE2b & FE2c & FE3a & FE4a & FE5a --> FE6["ErrorLogger.log()"]
        FE6 --> FE6a["Agregar timestamp + userAgent"]
        FE6a --> FE6b["Push a errorQueue en memoria"]
        FE6b --> FE6c["sendToBackend() vía fetch()<br/>(NO axios, evita recursión del interceptor)"]
        FE6c --> FE6d["POST /api/error-logs<br/>{type, message, stack, url,<br/>method, statusCode, context}"]
        FE6d --> FE7["ErrorLogController@store"]
        FE7 --> FE7a["Log::channel('frontend')<br/>->warning(payload)"]
        FE7a --> FE7b["FrontendErrorDatabaseLogger<br/>escribe a base de datos"]

        FE6c --> FE8{"¿Envío falló?"}
        FE8 -- "Sí" --> FE8a["Mantener en cola para retry<br/>Recortar cola si > 50 entradas"]
        FE8 -- "No" --> FE8b["Remover de la cola"]
    end

    subgraph "Canales de Log"
        LOG1["database_backend_request<br/>(BackendRequestLogger)<br/>Excepciones del servidor + errores de API"]
        LOG2["frontend<br/>(Stack: frontend_file + frontend_database)<br/>Errores Vue/JS/Axios desde el navegador"]
        LOG2a["frontend_database<br/>(FrontendErrorDatabaseLogger)<br/>Sub-canal de base de datos para errores de frontend"]
        LOG3["database<br/>(DatabaseLogger)<br/>Logging general de la aplicación"]
    end

    subgraph "Visualización de Error al Usuario"
        UD1["Web en Producción (4xx/5xx):<br/>Componente Inertia ErrorPage"]
        UD2["Web Local/Staging:<br/>Página de debug de Laravel<br/>(stack trace de Ignition)"]
        UD3["Respuestas API/JSON:<br/>JSON estructurado con<br/>tipo de error + status + mensaje"]
        UD4["Errores de validación (formularios Inertia):<br/>Retornados como prop errors compartida,<br/>mostrados inline vía useForm()"]
        UD5["CSRF 419 (JSON):<br/>'Por favor recarga la pagina'"]
        UD6["CSRF 419 (Web):<br/>Redirigir de regreso con mensaje flash"]
        UD7["Bloqueos de middleware de negocio:<br/>Redirigir a home/profile<br/>con mensaje de alerta"]
    end

7. Tabla Resumen: Tipos de Request y sus Rutas

Tipo de RequestPunto de EntradaGrupo de MiddlewareMiddleware ClaveFormato de RespuestaRuta de Error
Web (público)nginx -> public/index.phpweb (HandleAppearance, HandleInertiaRequests, AddLinkHeadersForPreloadedAssets)NingunoInertia (HTML o JSON parcial)ErrorPage (prod) o Ignition (dev)
Web (auth cliente)nginx -> public/index.phpwebauth, cliente_registro_completo, verificar_cliente_presenta_moraRenderizado o redirect InertiaRedirigir a login o perfil
Web (auth aliado)nginx -> public/index.phpwebauth:appRenderizado InertiaRedirigir a aliados/ingresar
Web (flujo de crédito)nginx -> public/index.phpwebauth, check_intentos_limite_diariosJSON vía axiosValidationException (422)
API (webhook)nginx -> public/index.phpapi (stateless, throttle)Ninguno (sin auth)JSON {success, data}JSON predeterminado del framework (ApiExceptionHandler invocado pero respuesta descartada — ver preámbulo)
API (error-logs)nginx -> public/index.phpapiNingunoJSON {success: true}Falla silenciosa
Queue Jobartisan queue:workN/AN/AN/A (async)Retry con backoff, luego tabla failed_jobs
Tarea Programadaartisan schedule:runN/AN/AN/A (async)Manejo estándar de fallas de jobs
Renderizado SSRServidor SSR de Node.jsN/AN/AString HTML pre-renderizadoCae a CSR en falla de SSR