Cambios realizados v1.2.11 (Backend) + v1.3.8 (Frontend):

Backend v1.2.11:
Nueva Funcionalidad - Control de Generación de PDF:
Campo nuevo: generate_pdf en modelo Checklist (Boolean, default: True)
Lógica modificada: Al completar inspección se verifica si el checklist tiene habilitada la generación de PDF
Comportamiento:
Si generate_pdf = True → Se genera y guarda el PDF automáticamente
Si generate_pdf = False → No se genera PDF, pdf_url queda en NULL
Logs informativos: Muestra en consola si el PDF se generó o se omitió
Frontend v1.3.8:
Interfaz para Control de PDF:
Checkbox nuevo en modal de edición de checklist: "Generar PDF automáticamente al completar inspección"
Estado por defecto: Activado (mantiene comportamiento actual)
Persistencia: El valor se guarda en la base de datos al editar checklist
Dónde está:
Admin → Checklists → Click en "✏️ Editar" de cualquier checklist
Debajo del checkbox de "Habilitar sistema de puntuación"
This commit is contained in:
2025-12-08 09:44:33 -03:00
parent 7fd37d0992
commit 0c0812efe9
8 changed files with 175 additions and 46 deletions

View File

@@ -2302,7 +2302,8 @@ function ChecklistsTab({ checklists, user, onChecklistCreated, onStartInspection
name: '',
description: '',
ai_mode: 'off',
scoring_enabled: true
scoring_enabled: true,
generate_pdf: true
})
const [editPermissionsData, setEditPermissionsData] = useState({
mechanic_ids: []
@@ -2673,7 +2674,8 @@ function ChecklistsTab({ checklists, user, onChecklistCreated, onStartInspection
name: checklist.name,
description: checklist.description || '',
ai_mode: checklist.ai_mode || 'off',
scoring_enabled: checklist.scoring_enabled ?? true
scoring_enabled: checklist.scoring_enabled ?? true,
generate_pdf: checklist.generate_pdf ?? true
})
setShowEditChecklistModal(true)
}}
@@ -3049,6 +3051,18 @@ function ChecklistsTab({ checklists, user, onChecklistCreated, onStartInspection
</label>
</div>
<div className="flex items-center">
<input
type="checkbox"
checked={editChecklistData.generate_pdf !== false}
onChange={(e) => setEditChecklistData({ ...editChecklistData, generate_pdf: e.target.checked })}
className="w-4 h-4 text-indigo-600 border-gray-300 rounded focus:ring-indigo-500"
/>
<label className="ml-2 text-sm text-gray-700">
Generar PDF automáticamente al completar inspección
</label>
</div>
<div className="bg-blue-50 border border-blue-200 rounded-lg p-4">
<p className="text-sm text-blue-800">
Los cambios se aplicarán inmediatamente. Las inspecciones existentes no se verán afectadas.
@@ -3061,7 +3075,7 @@ function ChecklistsTab({ checklists, user, onChecklistCreated, onStartInspection
onClick={() => {
setShowEditChecklistModal(false)
setSelectedChecklist(null)
setEditChecklistData({ name: '', description: '', ai_mode: 'off', scoring_enabled: true })
setEditChecklistData({ name: '', description: '', ai_mode: 'off', scoring_enabled: true, generate_pdf: true })
}}
className="flex-1 px-4 py-2 border border-gray-300 text-gray-700 rounded-lg hover:bg-gray-50 transition"
disabled={updating}
@@ -3264,6 +3278,9 @@ function InspectionDetailModal({ inspection, user, onClose, onUpdate, onContinue
const [showAuditLog, setShowAuditLog] = useState(false)
const [auditLogs, setAuditLogs] = useState([])
const [loadingAudit, setLoadingAudit] = useState(false)
const [showChatHistory, setShowChatHistory] = useState(false)
const [selectedChatHistory, setSelectedChatHistory] = useState(null)
const [selectedQuestionText, setSelectedQuestionText] = useState('')
// Función helper para convertir valores técnicos a etiquetas legibles
const getReadableAnswer = (answerValue, questionOptions) => {
@@ -3698,6 +3715,37 @@ function InspectionDetailModal({ inspection, user, onClose, onUpdate, onContinue
</div>
)}
{/* Chat History - SOLO PARA ADMINS */}
{user?.role === 'admin' && answer.chat_history && (
<div className="bg-blue-50 border border-blue-200 rounded p-3 mt-2">
<div className="flex items-center justify-between">
<span className="text-xs text-blue-800 font-semibold">💬 Historial de Chat IA</span>
<button
onClick={() => {
setSelectedChatHistory(answer.chat_history)
setSelectedQuestionText(question.text)
setShowChatHistory(true)
}}
className="px-2 py-1 bg-blue-600 text-white rounded hover:bg-blue-700 text-xs"
>
Ver Conversación
</button>
</div>
<div className="text-xs text-blue-700 mt-1">
{(() => {
try {
const chatData = typeof answer.chat_history === 'string'
? JSON.parse(answer.chat_history)
: answer.chat_history
return `${chatData.length} mensajes`
} catch {
return 'Historial disponible'
}
})()}
</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">
@@ -4018,6 +4066,88 @@ function InspectionDetailModal({ inspection, user, onClose, onUpdate, onContinue
</div>
</div>
)}
{/* Modal de Historial de Chat */}
{showChatHistory && selectedChatHistory && (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-[60] p-4">
<div className="bg-white rounded-lg max-w-4xl w-full max-h-[90vh] flex flex-col">
<div className="bg-gradient-to-r from-blue-600 to-purple-600 text-white p-4">
<div className="flex justify-between items-start">
<div>
<h3 className="text-xl font-bold">💬 Historial de Chat IA</h3>
<p className="text-sm opacity-90 mt-1">{selectedQuestionText}</p>
</div>
<button
onClick={() => setShowChatHistory(false)}
className="text-white hover:bg-white hover:bg-opacity-20 rounded-lg p-2 transition"
>
</button>
</div>
</div>
<div className="flex-1 overflow-y-auto p-4 space-y-3">
{(() => {
try {
const chatData = typeof selectedChatHistory === 'string'
? JSON.parse(selectedChatHistory)
: selectedChatHistory
return chatData.map((msg, idx) => (
<div
key={idx}
className={`flex ${msg.role === 'user' ? 'justify-end' : 'justify-start'}`}
>
<div
className={`max-w-[80%] rounded-lg p-3 ${
msg.role === 'user'
? 'bg-blue-600 text-white'
: 'bg-gray-100 text-gray-800 border border-gray-200'
}`}
>
<div className="flex items-center gap-2 mb-1">
<span className="text-xs font-semibold opacity-75">
{msg.role === 'user' ? '👤 Mecánico' : '🤖 Asistente IA'}
</span>
<span className="text-xs opacity-60">
{new Date(msg.timestamp).toLocaleTimeString('es-ES', {
hour: '2-digit',
minute: '2-digit'
})}
</span>
</div>
<div className="text-sm whitespace-pre-wrap break-words">
{msg.content}
</div>
{msg.confidence && (
<div className="text-xs mt-2 opacity-75">
Confianza: {Math.round(msg.confidence * 100)}%
</div>
)}
</div>
</div>
))
} catch (error) {
return (
<div className="text-center text-red-600">
Error al cargar el historial de chat
</div>
)
}
})()}
</div>
<div className="border-t p-4 bg-gray-50">
<button
onClick={() => setShowChatHistory(false)}
className="w-full px-4 py-2 bg-gray-600 text-white rounded-lg hover:bg-gray-700 transition"
>
Cerrar
</button>
</div>
</div>
</div>
)}
</div>
)
}

View File

@@ -153,7 +153,7 @@ export default function Sidebar({ user, activeTab, setActiveTab, sidebarOpen, se
className="w-10 h-10 object-contain bg-white rounded p-1"
/>
<p className="text-xs text-indigo-300 font-medium hover:text-indigo-200">
Ayutec v1.3.6
Ayutec v1.3.8
</p>
</a>
</div>