From 0c0812efe91e1ebee7c99a82e26daa6b419eb033 Mon Sep 17 00:00:00 2001 From: ronalds Date: Mon, 8 Dec 2025 09:44:33 -0300 Subject: [PATCH] Cambios realizados v1.2.11 (Backend) + v1.3.8 (Frontend): MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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" --- backend/app/main.py | 62 +++----- backend/app/models.py | 1 + backend/app/schemas.py | 3 + frontend/package.json | 2 +- frontend/public/service-worker.js | 2 +- frontend/src/App.jsx | 136 +++++++++++++++++- frontend/src/Sidebar.jsx | 2 +- migrations/add_generate_pdf_to_checklists.sql | 13 ++ 8 files changed, 175 insertions(+), 46 deletions(-) create mode 100644 migrations/add_generate_pdf_to_checklists.sql diff --git a/backend/app/main.py b/backend/app/main.py index dac0bb4..e81a572 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -278,7 +278,7 @@ def extract_pdf_text_smart(pdf_content: bytes, max_chars: int = None) -> dict: } -BACKEND_VERSION = "1.2.9" +BACKEND_VERSION = "1.2.11" app = FastAPI(title="Checklist Inteligente API", version=BACKEND_VERSION) # S3/MinIO configuration @@ -2049,54 +2049,30 @@ def generate_inspection_pdf(inspection_id: int, db: Session) -> str: # ===== LÓGICA ESPECIAL PARA AI_ASSISTANT ===== if is_ai_assistant and ans.chat_history: - # Generar resumen estructurado del chat - import asyncio + # Mostrar resumen simple SIN generar con IA (para evitar lentitud y peso) try: - # Ejecutar función async de forma sincrónica - loop = asyncio.new_event_loop() - asyncio.set_event_loop(loop) - chat_summary = loop.run_until_complete( - generate_chat_summary(ans.chat_history, question.text) - ) - loop.close() + chat_data = ans.chat_history if isinstance(ans.chat_history, list) else json.loads(ans.chat_history) + total_messages = len(chat_data) + user_messages = sum(1 for m in chat_data if m.get('role') == 'user') + assistant_messages = sum(1 for m in chat_data if m.get('role') == 'assistant') - # Renderizar informe narrativo question_data.append([ - Paragraph(f"💬 INFORME DE DIAGNÓSTICO ASISTIDO", + Paragraph(f"💬 DIAGNÓSTICO ASISTIDO POR IA", ParagraphStyle('chat_title', parent=answer_style, fontSize=11, textColor=colors.HexColor('#2563eb'), fontName='Helvetica-Bold')) ]) - # Problema identificado question_data.append([ - Paragraph(f"🔍 Problema Identificado:
{chat_summary.get('problema_identificado', 'N/A')}", + Paragraph(f"📊 Resumen de Conversación:
" + f"• Total de mensajes: {total_messages}
" + f"• Consultas del mecánico: {user_messages}
" + f"• Respuestas del asistente: {assistant_messages}

" + f"Nota: El historial completo está disponible en el sistema para administradores.", comment_style) ]) - # Hallazgos - if chat_summary.get('hallazgos') and len(chat_summary['hallazgos']) > 0: - hallazgos_text = "
".join([f"• {h}" for h in chat_summary['hallazgos']]) - question_data.append([ - Paragraph(f"📋 Hallazgos:
{hallazgos_text}", comment_style) - ]) - - # Diagnóstico - question_data.append([ - Paragraph(f"🔧 Diagnóstico:
{chat_summary.get('diagnostico', 'N/A')}", - comment_style) - ]) - - # Recomendaciones - if chat_summary.get('recomendaciones') and len(chat_summary['recomendaciones']) > 0: - recomendaciones_text = "
".join([f"• {r}" for r in chat_summary['recomendaciones']]) - question_data.append([ - Paragraph(f"✅ Recomendaciones:
{recomendaciones_text}", - ParagraphStyle('recommendations', parent=comment_style, - textColor=colors.HexColor('#16a34a'))) - ]) - except Exception as e: - print(f"❌ Error generando resumen de chat en PDF: {e}") + print(f"❌ Error procesando chat en PDF: {e}") # Fallback: mostrar que hubo conversación question_data.append([ Table([ @@ -2284,9 +2260,15 @@ def complete_inspection( inspection.status = "completed" inspection.completed_at = datetime.utcnow() - # Generar PDF usando función reutilizable - pdf_url = generate_inspection_pdf(inspection_id, db) - inspection.pdf_url = pdf_url + # Generar PDF solo si el checklist lo tiene habilitado + if inspection.checklist.generate_pdf: + pdf_url = generate_inspection_pdf(inspection_id, db) + inspection.pdf_url = pdf_url + print(f"✅ PDF generado para inspección #{inspection_id}") + else: + inspection.pdf_url = None + print(f"⏭️ PDF NO generado (deshabilitado en checklist) para inspección #{inspection_id}") + db.commit() db.refresh(inspection) diff --git a/backend/app/models.py b/backend/app/models.py index 6138ae0..5a9178e 100644 --- a/backend/app/models.py +++ b/backend/app/models.py @@ -47,6 +47,7 @@ class Checklist(Base): scoring_enabled = Column(Boolean, default=True) max_score = Column(Integer, default=0) logo_url = Column(String(500)) + generate_pdf = Column(Boolean, default=True) # Controla si se genera PDF al completar is_active = Column(Boolean, default=True) created_by = Column(Integer, ForeignKey("users.id")) created_at = Column(DateTime(timezone=True), server_default=func.now()) diff --git a/backend/app/schemas.py b/backend/app/schemas.py index 56066a8..8003f65 100644 --- a/backend/app/schemas.py +++ b/backend/app/schemas.py @@ -71,6 +71,7 @@ class ChecklistBase(BaseModel): ai_mode: str = "off" scoring_enabled: bool = True logo_url: Optional[str] = None + generate_pdf: bool = True class ChecklistCreate(ChecklistBase): mechanic_ids: Optional[List[int]] = [] # IDs de mecánicos autorizados @@ -81,12 +82,14 @@ class ChecklistUpdate(BaseModel): ai_mode: Optional[str] = None scoring_enabled: Optional[bool] = None logo_url: Optional[str] = None + generate_pdf: Optional[bool] = None is_active: Optional[bool] = None mechanic_ids: Optional[List[int]] = None # IDs de mecánicos autorizados class Checklist(ChecklistBase): id: int max_score: int + generate_pdf: bool is_active: bool created_by: int created_at: datetime diff --git a/frontend/package.json b/frontend/package.json index 33ded95..907c90e 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,7 +1,7 @@ { "name": "checklist-frontend", "private": true, - "version": "1.3.6", + "version": "1.3.8", "type": "module", "scripts": { "dev": "vite", diff --git a/frontend/public/service-worker.js b/frontend/public/service-worker.js index 99e9fb1..464b954 100644 --- a/frontend/public/service-worker.js +++ b/frontend/public/service-worker.js @@ -1,6 +1,6 @@ // Service Worker para PWA con detección de actualizaciones // IMPORTANTE: Actualizar esta versión cada vez que se despliegue una nueva versión -const CACHE_NAME = 'ayutec-v1.3.6'; +const CACHE_NAME = 'ayutec-v1.3.8'; const urlsToCache = [ '/', '/index.html' diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index e784da6..f8fafe9 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -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 +
+ setEditChecklistData({ ...editChecklistData, generate_pdf: e.target.checked })} + className="w-4 h-4 text-indigo-600 border-gray-300 rounded focus:ring-indigo-500" + /> + +
+

ℹ️ 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

)} + {/* Chat History - SOLO PARA ADMINS */} + {user?.role === 'admin' && answer.chat_history && ( +
+
+ 💬 Historial de Chat IA + +
+
+ {(() => { + try { + const chatData = typeof answer.chat_history === 'string' + ? JSON.parse(answer.chat_history) + : answer.chat_history + return `${chatData.length} mensajes` + } catch { + return 'Historial disponible' + } + })()} +
+
+ )} + {/* Photos - NUEVO: miniaturas de media_files */} {(answer.media_files && answer.media_files.length > 0) && (
@@ -4018,6 +4066,88 @@ function InspectionDetailModal({ inspection, user, onClose, onUpdate, onContinue
)} + + {/* Modal de Historial de Chat */} + {showChatHistory && selectedChatHistory && ( +
+
+
+
+
+

💬 Historial de Chat IA

+

{selectedQuestionText}

+
+ +
+
+ +
+ {(() => { + try { + const chatData = typeof selectedChatHistory === 'string' + ? JSON.parse(selectedChatHistory) + : selectedChatHistory + + return chatData.map((msg, idx) => ( +
+
+
+ + {msg.role === 'user' ? '👤 Mecánico' : '🤖 Asistente IA'} + + + {new Date(msg.timestamp).toLocaleTimeString('es-ES', { + hour: '2-digit', + minute: '2-digit' + })} + +
+
+ {msg.content} +
+ {msg.confidence && ( +
+ Confianza: {Math.round(msg.confidence * 100)}% +
+ )} +
+
+ )) + } catch (error) { + return ( +
+ Error al cargar el historial de chat +
+ ) + } + })()} +
+ +
+ +
+
+
+ )} ) } diff --git a/frontend/src/Sidebar.jsx b/frontend/src/Sidebar.jsx index 7cd0cf4..1f19e9f 100644 --- a/frontend/src/Sidebar.jsx +++ b/frontend/src/Sidebar.jsx @@ -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" />

- Ayutec v1.3.6 + Ayutec v1.3.8

diff --git a/migrations/add_generate_pdf_to_checklists.sql b/migrations/add_generate_pdf_to_checklists.sql new file mode 100644 index 0000000..69c9004 --- /dev/null +++ b/migrations/add_generate_pdf_to_checklists.sql @@ -0,0 +1,13 @@ +-- Agregar campo generate_pdf a la tabla checklists +-- Este campo controla si se genera PDF automáticamente al completar una inspección + +ALTER TABLE checklists +ADD COLUMN IF NOT EXISTS generate_pdf BOOLEAN DEFAULT TRUE; + +-- Actualizar checklists existentes para que generen PDF por defecto +UPDATE checklists +SET generate_pdf = TRUE +WHERE generate_pdf IS NULL; + +-- Comentario para documentación +COMMENT ON COLUMN checklists.generate_pdf IS 'Controla si se genera PDF automáticamente al completar inspección';