✅ 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:
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "checklist-frontend",
|
||||
"private": true,
|
||||
"version": "1.0.80",
|
||||
"version": "1.0.81",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -3373,6 +3373,36 @@ function InspectionDetailModal({ inspection, user, onClose, onUpdate, onContinue
|
||||
</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 */}
|
||||
{(answer.media_files && answer.media_files.length > 0) && (
|
||||
<div className="flex gap-2 flex-wrap mt-2">
|
||||
@@ -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 ? (
|
||||
<>
|
||||
<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>Analizar Pregunta</span>
|
||||
<span>📁</span>
|
||||
<span>Cargar Documentos</span>
|
||||
</>
|
||||
)}
|
||||
</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="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>
|
||||
<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>
|
||||
)}
|
||||
|
||||
{answers[currentQuestion.id]?.photos?.length > 0 && !aiAnalyzing && (
|
||||
<div className="text-sm text-gray-600 mt-1">
|
||||
{checklist.ai_mode === 'full' && answers[currentQuestion.id]?.value && (
|
||||
<span className="text-green-600">✓ Analizada</span>
|
||||
)}
|
||||
{/* Comentarios removidos que revelaban IA */}
|
||||
{checklist.ai_mode === 'assisted' && answers[currentQuestion.id]?.observations.includes('[IA Sugiere') && (
|
||||
<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')
|
||||
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 && (
|
||||
<div className="text-sm text-green-600 mt-2 flex items-center gap-1">
|
||||
<span>✓</span>
|
||||
<span>Respuesta guardada automáticamente</span>
|
||||
<span>Respuesta guardada</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user