Cambios Completados - IA Oculta al Mecánico

🎭 Frontend v1.0.81
1. Botón renombrado:

 Antes: "🤖 Analizar Pregunta"
 Ahora: "📁 Cargar Documentos"
Estado procesando: "Procesando..." (sin mencionar IA)
2. Análisis IA separado de observaciones:

El análisis NO se escribe en el campo de observaciones
Se guarda en aiAnalysis (campo separado)
Mecánico escribe observaciones manualmente
Se agrega flag documentsLoaded: true al procesar
3. Popup de confirmación:

Después de cargar documentos: " Documentos cargados correctamente"
NO muestra el análisis al mecánico
4. Validación obligatoria:

Si hay fotos adjuntas y el checklist tiene IA activada
DEBE presionar "Cargar Documentos" antes de continuar
Mensaje: "⚠️ Debes presionar 'Cargar Documentos' antes de continuar"
5. Referencias a IA eliminadas:

 Removido: "Analizando X imagen(es) con IA..."
 Removido: "✓ Analizada"
 Removido: "guardada automáticamente"
 Ahora: "Procesando X documento(s)..."
 Ahora: "Respuesta guardada"
6. Análisis IA solo visible para admin:

En el modal de detalle de inspección
Sección morada "🤖 Análisis de IA"
Muestra: estado, observaciones, recomendación, confianza
Solo visible si user.role === 'admin'
🔧 Backend v1.0.83
Sin cambios (el campo ai_analysis ya existía en JSON)
This commit is contained in:
2025-11-29 08:40:14 -03:00
parent 00218a1a92
commit 886f0bafbd
3 changed files with 66 additions and 134 deletions

View File

@@ -204,7 +204,7 @@ def send_completed_inspection_to_n8n(inspection, db):
# No lanzamos excepción para no interrumpir el flujo normal # 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) app = FastAPI(title="Checklist Inteligente API", version=BACKEND_VERSION)
# S3/MinIO configuration # S3/MinIO configuration

View File

@@ -1,7 +1,7 @@
{ {
"name": "checklist-frontend", "name": "checklist-frontend",
"private": true, "private": true,
"version": "1.0.80", "version": "1.0.81",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",

View File

@@ -3373,6 +3373,36 @@ function InspectionDetailModal({ inspection, user, onClose, onUpdate, onContinue
</div> </div>
)} )}
{/* AI Analysis - SOLO VISIBLE PARA ADMIN */}
{user?.role === 'admin' && answer.ai_analysis && (
<div className="bg-purple-50 border border-purple-200 rounded p-3">
<div className="flex items-center gap-2 mb-2">
<span className="text-xs text-purple-800 font-semibold">🤖 Análisis de IA</span>
</div>
<div className="text-xs text-purple-900 space-y-1">
{Array.isArray(answer.ai_analysis) ? (
answer.ai_analysis.map((analysis, idx) => (
<div key={idx} className="border-b border-purple-200 last:border-0 pb-2">
<div className="font-medium">📸 Imagen {idx + 1}:</div>
{analysis.analysis && typeof analysis.analysis === 'object' ? (
<>
<div>Estado: <span className="font-semibold">{analysis.analysis.status?.toUpperCase()}</span></div>
{analysis.analysis.observations && <div>Observaciones: {analysis.analysis.observations}</div>}
{analysis.analysis.recommendation && <div>Recomendación: {analysis.analysis.recommendation}</div>}
{analysis.analysis.confidence && <div>Confianza: {(analysis.analysis.confidence * 100).toFixed(0)}%</div>}
</>
) : (
<div>{JSON.stringify(analysis.analysis)}</div>
)}
</div>
))
) : (
<div>{JSON.stringify(answer.ai_analysis)}</div>
)}
</div>
</div>
)}
{/* Photos - NUEVO: miniaturas de media_files */} {/* Photos - NUEVO: miniaturas de media_files */}
{(answer.media_files && answer.media_files.length > 0) && ( {(answer.media_files && answer.media_files.length > 0) && (
<div className="flex gap-2 flex-wrap mt-2"> <div className="flex gap-2 flex-wrap mt-2">
@@ -4396,131 +4426,23 @@ function InspectionModal({ checklist, existingInspection, user, onClose, onCompl
if (analyses.length > 0) { if (analyses.length > 0) {
console.log('✅ Análisis recibidos:', analyses.length) console.log('✅ Análisis recibidos:', analyses.length)
let suggestedAnswer = null // Guardar el análisis de IA en un campo separado (NO en observaciones)
let observationsText = '' // El mecánico NO verá esto, solo el admin
let worstStatus = 'ok' // Track the worst status across all images setAnswers(prev => ({
...prev,
if (analyses.length === 1) { [questionId]: {
// Single image analysis ...(prev[questionId] || { value: '', observations: '', photos: [] }),
const firstResult = analyses[0] photos: files,
const analysis = firstResult.analysis aiAnalysis: analyses, // Guardar análisis separado
console.log('📊 Análisis de imagen única:', analysis) documentsLoaded: true // Marcar que se procesaron los documentos
// 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}`
} }
} 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 console.log(`✅ Análisis IA guardado (${analyses.length} análisis)`)
if (question.type === 'pass_fail') { console.log(`📝 Las observaciones quedan para que el mecánico las escriba manualmente`)
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'
}
}
// Mostrar popup de confirmación en vez de llenar observaciones
// In FULL mode, auto-fill the answer alert('✅ Documentos cargados correctamente')
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)
}
} }
} catch (error) { } catch (error) {
console.error('❌ Error al analizar fotos con IA:', error) console.error('❌ Error al analizar fotos con IA:', error)
@@ -4921,12 +4843,12 @@ function InspectionModal({ checklist, existingInspection, user, onClose, onCompl
{aiAnalyzing ? ( {aiAnalyzing ? (
<> <>
<div className="animate-spin h-4 w-4 border-2 border-white border-t-transparent rounded-full"></div> <div className="animate-spin h-4 w-4 border-2 border-white border-t-transparent rounded-full"></div>
<span>Analizando...</span> <span>Procesando...</span>
</> </>
) : ( ) : (
<> <>
<span>🤖</span> <span>📁</span>
<span>Analizar Pregunta</span> <span>Cargar Documentos</span>
</> </>
)} )}
</button> </button>
@@ -4938,16 +4860,12 @@ function InspectionModal({ checklist, existingInspection, user, onClose, onCompl
<div className="mt-2 p-3 bg-blue-50 border border-blue-200 rounded-lg"> <div className="mt-2 p-3 bg-blue-50 border border-blue-200 rounded-lg">
<div className="flex items-center gap-2 text-blue-700"> <div className="flex items-center gap-2 text-blue-700">
<div className="animate-spin h-4 w-4 border-2 border-blue-600 border-t-transparent rounded-full"></div> <div className="animate-spin h-4 w-4 border-2 border-blue-600 border-t-transparent rounded-full"></div>
<span className="text-sm font-medium">Analizando {answers[currentQuestion.id]?.photos?.length || 0} imagen(es) con IA...</span> <span className="text-sm font-medium">Procesando {answers[currentQuestion.id]?.photos?.length || 0} documento(s)...</span>
</div> </div>
</div> </div>
)} )}
{answers[currentQuestion.id]?.photos?.length > 0 && !aiAnalyzing && ( {/* Comentarios removidos que revelaban IA */}
<div className="text-sm text-gray-600 mt-1">
{checklist.ai_mode === 'full' && answers[currentQuestion.id]?.value && (
<span className="text-green-600"> Analizada</span>
)}
{checklist.ai_mode === 'assisted' && answers[currentQuestion.id]?.observations.includes('[IA Sugiere') && ( {checklist.ai_mode === 'assisted' && answers[currentQuestion.id]?.observations.includes('[IA Sugiere') && (
<span className="text-blue-600"> Sugerencia generada</span> <span className="text-blue-600"> Sugerencia generada</span>
)} )}
@@ -4980,6 +4898,13 @@ function InspectionModal({ checklist, existingInspection, user, onClose, onCompl
alert('⚠️ Debes subir al menos una fotografía para esta pregunta') alert('⚠️ Debes subir al menos una fotografía para esta pregunta')
return 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) saveAnswer(currentQuestion.id)
goToQuestion(currentQuestionIndex + 1) 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') alert('⚠️ Debes subir al menos una fotografía para esta pregunta')
return 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) saveAnswer(currentQuestion.id)
proceedToSignatures() proceedToSignatures()
}} }}
@@ -5009,7 +4941,7 @@ function InspectionModal({ checklist, existingInspection, user, onClose, onCompl
{answers[currentQuestion.id]?.value && ( {answers[currentQuestion.id]?.value && (
<div className="text-sm text-green-600 mt-2 flex items-center gap-1"> <div className="text-sm text-green-600 mt-2 flex items-center gap-1">
<span></span> <span></span>
<span>Respuesta guardada automáticamente</span> <span>Respuesta guardada</span>
</div> </div>
)} )}
</div> </div>