diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index b7d9e83..031629b 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -3343,7 +3343,14 @@ function InspectionModal({ checklist, user, onClose, onComplete }) { } const handlePhotoChange = async (questionId, files) => { - const filesArray = Array.from(files) + const question = questions.find(q => q.id === questionId) + let filesArray = Array.from(files) + + // Validar límite de fotos + if (question.max_photos && filesArray.length > question.max_photos) { + alert(`⚠️ Solo puedes subir hasta ${question.max_photos} foto${question.max_photos > 1 ? 's' : ''} para esta pregunta`) + filesArray = filesArray.slice(0, question.max_photos) + } // Update photos immediately setAnswers(prev => ({ @@ -3381,9 +3388,12 @@ function InspectionModal({ checklist, user, onClose, onComplete }) { console.log(`📝 Usando prompt personalizado: ${question.ai_prompt.substring(0, 50)}...`) } - // Analyze each photo + // Analyze each photo sequentially const analyses = [] - for (const file of files) { + for (let i = 0; i < files.length; i++) { + const file = files[i] + console.log(`📸 Analizando foto ${i + 1} de ${files.length}: ${file.name}`) + const formData = new FormData() formData.append('file', file) formData.append('question_id', question.id.toString()) @@ -3392,6 +3402,7 @@ function InspectionModal({ checklist, user, onClose, onComplete }) { console.log(' - question_id:', question.id) console.log(' - question.text:', question.text) console.log(' - question.ai_prompt:', question.ai_prompt || 'NO TIENE') + console.log(' - imagen:', i + 1, 'de', files.length) // Include inspection_id for vehicle context if (inspectionId) { @@ -3424,8 +3435,8 @@ function InspectionModal({ checklist, user, onClose, onComplete }) { // Check if AI analysis was successful if (result.success && result.analysis) { - analyses.push(result) - console.log('✅ Análisis IA exitoso') + analyses.push({ ...result, imageIndex: i + 1, fileName: file.name }) + console.log('✅ Análisis IA exitoso para imagen', i + 1) console.log(' - Provider:', result.provider) console.log(' - Model:', result.model) console.log(' - Status:', result.analysis.status) @@ -3435,6 +3446,7 @@ function InspectionModal({ checklist, user, onClose, onComplete }) { // Show user-friendly error if (result.error && result.error.includes('No AI configuration')) { alert('⚙️ Por favor configura tu API key en Configuración primero.') + break // Stop analyzing if no API key } } } else { @@ -3445,65 +3457,90 @@ function InspectionModal({ checklist, user, onClose, onComplete }) { if (analyses.length > 0) { console.log('✅ Análisis recibidos:', analyses.length) - // Process analysis results - const firstResult = analyses[0] - const analysis = firstResult.analysis - console.log('📊 Análisis completo:', analysis) + let suggestedAnswer = null let observationsText = '' + let worstStatus = 'ok' // Track the worst status across all images + + if (analyses.length === 1) { + // Single image analysis + const firstResult = analyses[0] + const analysis = firstResult.analysis + console.log('📊 Análisis de imagen única:', analysis) - // Check if analysis is an object (structured JSON response) - if (typeof analysis === 'object' && analysis !== null) { - // Extract structured information - const status = analysis.status || 'ok' - const observations = analysis.observations || '' - const recommendation = analysis.recommendation || '' - const confidence = analysis.confidence || 0.7 + // Check if analysis is an object (structured JSON response) + if (typeof analysis === 'object' && analysis !== null) { + // Extract structured information + const status = analysis.status || 'ok' + const observations = analysis.observations || '' + const recommendation = analysis.recommendation || '' + const confidence = analysis.confidence || 0.7 - // Build observations text - observationsText = `🤖 Análisis IA (${(confidence * 100).toFixed(0)}% confianza):\n${observations}` - if (recommendation) { - observationsText += `\n\n💡 Recomendación: ${recommendation}` - } - - // Map status to answer based on question type - if (question.type === 'pass_fail') { - if (status === 'ok') { - suggestedAnswer = 'pass' - } else if (status === 'critical' || status === 'minor') { - suggestedAnswer = 'fail' - } - } else if (question.type === 'good_bad') { - if (status === 'ok') { - suggestedAnswer = 'good' - } else if (status === 'minor') { - suggestedAnswer = 'regular' - } else if (status === 'critical') { - suggestedAnswer = 'bad' + // Build observations text + observationsText = `🤖 Análisis IA (${(confidence * 100).toFixed(0)}% confianza):\n${observations}` + if (recommendation) { + observationsText += `\n\n💡 Recomendación: ${recommendation}` } + worstStatus = status + } else if (typeof analysis === 'string') { + observationsText = `🤖 Análisis IA:\n${analysis}` } - } else if (typeof analysis === 'string') { - // Fallback for plain text responses - observationsText = `🤖 Análisis IA:\n${analysis}` - const analysisLower = analysis.toLowerCase() + } else { + // Multiple images - summarize all analyses + console.log('📊 Resumen de', analyses.length, 'análisis:') + observationsText = `🤖 Análisis IA de ${analyses.length} imágenes:\n\n` - if (question.type === 'pass_fail') { - if (analysisLower.includes('pasa') || analysisLower.includes('correcto') || analysisLower.includes('bueno') || analysisLower.includes('ok')) { - suggestedAnswer = 'pass' - } else if (analysisLower.includes('falla') || analysisLower.includes('incorrecto') || analysisLower.includes('problema') || analysisLower.includes('critical')) { - suggestedAnswer = 'fail' - } - } else if (question.type === 'good_bad') { - if (analysisLower.includes('bueno') || analysisLower.includes('excelente')) { - suggestedAnswer = 'good' - } else if (analysisLower.includes('regular') || analysisLower.includes('aceptable')) { - suggestedAnswer = 'regular' - } else if (analysisLower.includes('malo') || analysisLower.includes('deficiente')) { - suggestedAnswer = 'bad' + const statusPriority = { 'critical': 3, 'minor': 2, 'warning': 2, 'ok': 1 } + let maxPriority = 0 + + analyses.forEach((result, index) => { + const analysis = result.analysis + observationsText += `📸 Imagen ${result.imageIndex}:\n` + + if (typeof analysis === 'object' && analysis !== null) { + const status = analysis.status || 'ok' + const observations = analysis.observations || '' + const confidence = analysis.confidence || 0.7 + + observationsText += ` Estado: ${status.toUpperCase()}` + observationsText += ` (${(confidence * 100).toFixed(0)}% confianza)\n` + observationsText += ` ${observations}\n` + + // Track worst status + const priority = statusPriority[status] || 1 + if (priority > maxPriority) { + maxPriority = priority + worstStatus = status + } + } else if (typeof analysis === 'string') { + observationsText += ` ${analysis}\n` } + observationsText += '\n' + }) + + // Add overall recommendation + observationsText += `📋 Resumen General:\n` + observationsText += ` Estado más crítico detectado: ${worstStatus.toUpperCase()}\n` + } + + // Map worst status to answer + if (question.type === 'pass_fail') { + if (worstStatus === 'ok') { + suggestedAnswer = 'pass' + } else if (worstStatus === 'critical' || worstStatus === 'minor') { + suggestedAnswer = 'fail' + } + } else if (question.type === 'good_bad') { + if (worstStatus === 'ok') { + suggestedAnswer = 'good' + } else if (worstStatus === 'minor' || worstStatus === 'warning') { + suggestedAnswer = 'regular' + } else if (worstStatus === 'critical') { + suggestedAnswer = 'bad' } } - + + // In FULL mode, auto-fill the answer if (checklist.ai_mode === 'full' && suggestedAnswer) { setAnswers(prev => ({ @@ -3513,7 +3550,7 @@ function InspectionModal({ checklist, user, onClose, onComplete }) { value: suggestedAnswer, observations: observationsText, photos: files, - aiAnalysis: firstResult.analysis // Guardar análisis completo de IA + aiAnalysis: analyses // Guardar todos los análisis } })) console.log(`🤖 FULL MODE: Respuesta auto-completada con: ${suggestedAnswer}`) @@ -3883,7 +3920,12 @@ function InspectionModal({ checklist, user, onClose, onComplete }) { {currentQuestion.allow_photos && (