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:
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user