# Sistema de Timeout y Retry para Chat ## 📋 Descripción Sistema implementado para evitar que los usuarios queden atascados esperando indefinidamente cuando el API del chatbot demora demasiado en responder. ## ✨ Características ### 1. Timeout Automático (15 segundos) - Todas las peticiones al API de chat tienen un límite máximo de **15 segundos** - Si la petición excede este tiempo, se cancela automáticamente usando `AbortController` - El usuario es notificado cuando esto ocurre ### 2. Retry Automático - Si una petición expira por timeout, el sistema **reintenta automáticamente una vez** - El mensaje del usuario se preserva y se reenvía - Se muestra una notificación informando al usuario del reintento ### 3. Mensajes Progresivos en el Loader El componente `DynamicLoader` ahora incluye mensajes optimizados que se muestran durante la carga: - 0-4s: "Asistente escribiendo..." - 4-8s: "Analizando tu consulta..." - 8-12s: "Preparando tu respuesta..." - 12-20s: "Casi listo..." Los mensajes están diseñados para cubrir hasta 20 segundos (tiempo máximo incluyendo márgenes). ## 🔧 Implementación Técnica ### useChat.ts ```typescript // Configurar timeout de 15 segundos const controller = new AbortController(); const timeoutId = setTimeout(() => { console.log("⏰ [CHAT] Timeout alcanzado, cancelando petición..."); controller.abort(); }, 15000); // 15 segundos const response = await fetch("/api/chat", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ message, messages, chatType }), signal: controller.signal, // 🔑 Señal de cancelación }); // Limpiar timeout si la petición fue exitosa clearTimeout(timeoutId); ``` ### Manejo del Error de Timeout ```typescript catch (error) { clearTimeout(timeoutId); // Detectar si el error fue por timeout const isTimeout = error instanceof Error && error.name === 'AbortError'; // Si fue timeout y es el primer intento, reintentar if (isTimeout && retryCount === 0) { toast.info("Reintentando...", { description: "La petición tardó demasiado, reintentando automáticamente.", }); // Reintentar después de 1 segundo setTimeout(() => sendMessage(messageToSend, 1), 1000); return; } // Mostrar mensaje de error personalizado const errorContent = isTimeout ? "La petición tardó demasiado tiempo y fue cancelada automáticamente..." : "Lo siento, estoy teniendo problemas técnicos..."; } ``` ## 📊 Flujo de Usuario ``` Usuario envía mensaje ↓ Petición al API inicia (timeout = 15s) ↓ ¿Respuesta antes de 15s? ├─ SÍ → Mostrar respuesta ✅ └─ NO → Cancelar petición ↓ ¿Es el primer intento? ├─ SÍ → Mostrar toast "Reintentando..." │ Reintentar automáticamente (1 vez) │ ↓ │ ¿Respuesta antes de 15s? │ ├─ SÍ → Mostrar respuesta ✅ │ └─ NO → Mostrar mensaje de error ❌ │ └─ NO → Mostrar mensaje de error ❌ ``` ## 🎯 Beneficios 1. **Mejor UX**: Los usuarios nunca quedan atascados indefinidamente 2. **Respuesta rápida**: 15 segundos es un tiempo óptimo para mantener la atención del usuario 3. **Feedback claro**: Mensajes progresivos informan al usuario del estado 4. **Resiliente**: El sistema intenta recuperarse automáticamente 5. **Sin cambios en el backend**: Solo cambios en el frontend 6. **Fácil de mantener**: Usa APIs nativas del navegador (`AbortController`) 7. **UX moderna**: Tiempos de espera competitivos con apps modernas (ChatGPT, Claude, etc.) ## 🔧 Configuración ### Ajustar el tiempo de timeout ```typescript // En src/hooks/useChat.ts, línea ~183 const timeoutId = setTimeout(() => { controller.abort(); }, 15000); // 👈 Cambiar este valor (en milisegundos) // 15000 = 15 segundos (actual - óptimo para UX) // 10000 = 10 segundos (más agresivo) // 20000 = 20 segundos (más tolerante) ``` ### Ajustar el número de reintentos ```typescript // En src/hooks/useChat.ts, línea ~401 if (isTimeout && retryCount === 0) { // 👈 Cambiar la condición // retryCount === 0: Solo 1 reintento // retryCount < 2: Hasta 2 reintentos // etc. } ``` ### Personalizar mensajes del loader Editar el array `loadingStates` en `src/components/chatbot/DynamicLoader.tsx`: ```typescript const loadingStates: LoadingState[] = [ { message: "Tu mensaje personalizado...", icon: , duration: 5 // segundos que dura este mensaje }, // ... más estados ]; ``` ## 📝 Notas Técnicas - **AbortController**: API nativa del navegador para cancelar peticiones fetch - **AbortError**: Error específico que se lanza cuando se cancela una petición - **Toast notifications**: Usa la librería `sonner` para notificaciones - **Estado del chat**: Se preserva correctamente durante los reintentos ## 🚀 Próximas Mejoras 1. **Timeout adaptativo**: Ajustar el timeout según la complejidad de la consulta 2. **Métricas**: Registrar tiempos de respuesta promedio 3. **Indicador visual**: Barra de progreso que muestre el tiempo transcurrido 4. **Configuración por usuario**: Permitir que los usuarios ajusten el timeout ## 🐛 Troubleshooting ### El timeout ocurre muy frecuentemente - Aumentar el valor de timeout (15000ms por defecto = 15 segundos) - Verificar la conexión del servidor - Revisar logs del API para identificar cuellos de botella - Considerar si el modelo de IA necesita optimización ### Los reintentos no funcionan - Verificar que `retryCount` se pase correctamente - Revisar logs de consola para errores - Asegurar que el estado se limpie correctamente antes del reintento ### El usuario ve el mensaje de timeout antes de tiempo - Los mensajes del loader están configurados para 20 segundos totales - El timeout está en 15 segundos para el primer intento - Con el reintento, el tiempo máximo total es ~31 segundos (15s + 1s delay + 15s) - Esto es ideal para mantener una UX ágil sin frustrar al usuario ## 📚 Referencias - [MDN - AbortController](https://developer.mozilla.org/en-US/docs/Web/API/AbortController) - [MDN - Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) - [Sonner Toast](https://sonner.emilkowal.ski/)