diff --git a/backend/app/main.py b/backend/app/main.py index 1711db9..8d351b3 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -204,7 +204,7 @@ def send_completed_inspection_to_n8n(inspection, db): # No lanzamos excepción para no interrumpir el flujo normal -BACKEND_VERSION = "1.0.82" +BACKEND_VERSION = "1.0.83" app = FastAPI(title="Checklist Inteligente API", version=BACKEND_VERSION) # S3/MinIO configuration diff --git a/frontend/package.json b/frontend/package.json index 729bdd7..e0838da 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,7 +1,7 @@ { "name": "checklist-frontend", "private": true, - "version": "1.0.80", + "version": "1.0.81", "type": "module", "scripts": { "dev": "vite", diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 0ca7a2c..11ae342 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -3373,6 +3373,36 @@ function InspectionDetailModal({ inspection, user, onClose, onUpdate, onContinue )} + {/* AI Analysis - SOLO VISIBLE PARA ADMIN */} + {user?.role === 'admin' && answer.ai_analysis && ( +
+
+ 🤖 Análisis de IA +
+
+ {Array.isArray(answer.ai_analysis) ? ( + answer.ai_analysis.map((analysis, idx) => ( +
+
📸 Imagen {idx + 1}:
+ {analysis.analysis && typeof analysis.analysis === 'object' ? ( + <> +
Estado: {analysis.analysis.status?.toUpperCase()}
+ {analysis.analysis.observations &&
Observaciones: {analysis.analysis.observations}
} + {analysis.analysis.recommendation &&
Recomendación: {analysis.analysis.recommendation}
} + {analysis.analysis.confidence &&
Confianza: {(analysis.analysis.confidence * 100).toFixed(0)}%
} + + ) : ( +
{JSON.stringify(analysis.analysis)}
+ )} +
+ )) + ) : ( +
{JSON.stringify(answer.ai_analysis)}
+ )} +
+
+ )} + {/* Photos - NUEVO: miniaturas de media_files */} {(answer.media_files && answer.media_files.length > 0) && (
@@ -4396,131 +4426,23 @@ function InspectionModal({ checklist, existingInspection, user, onClose, onCompl if (analyses.length > 0) { console.log('✅ Análisis recibidos:', analyses.length) - 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 - - // Build observations text - observationsText = `Análisis Automático (${(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 Automático:\n${analysis}` + // Guardar el análisis de IA en un campo separado (NO en observaciones) + // El mecánico NO verá esto, solo el admin + setAnswers(prev => ({ + ...prev, + [questionId]: { + ...(prev[questionId] || { value: '', observations: '', photos: [] }), + photos: files, + aiAnalysis: analyses, // Guardar análisis separado + documentsLoaded: true // Marcar que se procesaron los documentos } - } else { - // Multiple images - summarize all analyses - console.log('📊 Resumen de', analyses.length, 'análisis:') - observationsText = `Análisis Automático de ${analyses.length} imágenes:\n\n` - - 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' - } - } + console.log(`✅ Análisis IA guardado (${analyses.length} análisis)`) + console.log(`📝 Las observaciones quedan para que el mecánico las escriba manualmente`) - - // In FULL mode, auto-fill the answer - if (checklist.ai_mode === 'full' && suggestedAnswer) { - setAnswers(prev => ({ - ...prev, - [questionId]: { - ...(prev[questionId] || { value: '', observations: '', photos: [] }), - value: suggestedAnswer, - observations: observationsText, - photos: files, - aiAnalysis: analyses // Guardar todos los análisis - } - })) - console.log(`🤖 FULL MODE: Respuesta auto-completada con: ${suggestedAnswer}`) - console.log(`📝 Observaciones guardadas:`, observationsText) - } - // In ASSISTED mode, suggest in observations - else if (checklist.ai_mode === 'assisted') { - setAnswers(prev => ({ - ...prev, - [questionId]: { - ...(prev[questionId] || { value: '', observations: '', photos: [] }), - observations: `${suggestedAnswer ? `[IA Sugiere: ${suggestedAnswer}]\n` : ''}${observationsText}`, - photos: files, - aiAnalysis: analyses // Guardar todos los análisis - } - })) - console.log(`🤖 ASSISTED MODE: Sugerencia agregada a observaciones`) - console.log(`📝 Observaciones guardadas:`, `${suggestedAnswer ? `[IA Sugiere: ${suggestedAnswer}]\n` : ''}${observationsText}`) - } - // Siempre guardar observaciones incluso si no hay modo específico o sugerencia - else if (observationsText) { - setAnswers(prev => ({ - ...prev, - [questionId]: { - ...(prev[questionId] || { value: '', observations: '', photos: [] }), - observations: observationsText, - photos: files, - aiAnalysis: analyses // Guardar todos los análisis - } - })) - console.log(`🤖 Análisis IA guardado en observaciones:`, observationsText) - } + // Mostrar popup de confirmación en vez de llenar observaciones + alert('✅ Documentos cargados correctamente') } } catch (error) { console.error('❌ Error al analizar fotos con IA:', error) @@ -4921,12 +4843,12 @@ function InspectionModal({ checklist, existingInspection, user, onClose, onCompl {aiAnalyzing ? ( <>
- Analizando... + Procesando... ) : ( <> - 🤖 - Analizar Pregunta + 📁 + Cargar Documentos )} @@ -4938,16 +4860,12 @@ function InspectionModal({ checklist, existingInspection, user, onClose, onCompl
- Analizando {answers[currentQuestion.id]?.photos?.length || 0} imagen(es) con IA... + Procesando {answers[currentQuestion.id]?.photos?.length || 0} documento(s)...
)} - {answers[currentQuestion.id]?.photos?.length > 0 && !aiAnalyzing && ( -
- {checklist.ai_mode === 'full' && answers[currentQuestion.id]?.value && ( - ✓ Analizada - )} + {/* Comentarios removidos que revelaban IA */} {checklist.ai_mode === 'assisted' && answers[currentQuestion.id]?.observations.includes('[IA Sugiere') && ( ✓ Sugerencia generada )} @@ -4980,6 +4898,13 @@ function InspectionModal({ checklist, existingInspection, user, onClose, onCompl alert('⚠️ Debes subir al menos una fotografía para esta pregunta') return } + // Validar que se hayan cargado documentos si hay fotos y está en modo IA + if ((checklist.ai_mode === 'assisted' || checklist.ai_mode === 'full') && + answers[currentQuestion.id]?.photos?.length > 0 && + !answers[currentQuestion.id]?.documentsLoaded) { + alert('⚠️ Debes presionar "Cargar Documentos" antes de continuar') + return + } saveAnswer(currentQuestion.id) goToQuestion(currentQuestionIndex + 1) }} @@ -4995,6 +4920,13 @@ function InspectionModal({ checklist, existingInspection, user, onClose, onCompl alert('⚠️ Debes subir al menos una fotografía para esta pregunta') return } + // Validar que se hayan cargado documentos si hay fotos y está en modo IA + if ((checklist.ai_mode === 'assisted' || checklist.ai_mode === 'full') && + answers[currentQuestion.id]?.photos?.length > 0 && + !answers[currentQuestion.id]?.documentsLoaded) { + alert('⚠️ Debes presionar "Cargar Documentos" antes de continuar') + return + } saveAnswer(currentQuestion.id) proceedToSignatures() }} @@ -5009,7 +4941,7 @@ function InspectionModal({ checklist, existingInspection, user, onClose, onCompl {answers[currentQuestion.id]?.value && (
- Respuesta guardada automáticamente + Respuesta guardada
)}