From 822ab5a1cbea97b26940ef08156b7039e716022a Mon Sep 17 00:00:00 2001 From: ronalds Date: Wed, 26 Nov 2025 01:20:26 -0300 Subject: [PATCH] Actualziacion de analisis de ia con las imagenes y se agrega el campo de cod operario en el front y en el back --- backend/app/main.py | 88 ++++++++++++++--------- backend/app/models.py | 1 + backend/app/schemas.py | 3 + frontend/src/App.jsx | 59 +++++++++++++-- migrations/add_employee_code_to_users.sql | 16 +++++ 5 files changed, 129 insertions(+), 38 deletions(-) create mode 100644 migrations/add_employee_code_to_users.sql diff --git a/backend/app/main.py b/backend/app/main.py index fff6146..e3bcd7f 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -325,6 +325,7 @@ def create_user( username=user.username, email=user.email, full_name=user.full_name, + employee_code=user.employee_code, role=user.role, password_hash=hashed_password, is_active=True @@ -374,6 +375,9 @@ def update_user( if user_update.full_name is not None: db_user.full_name = user_update.full_name + if user_update.employee_code is not None: + db_user.employee_code = user_update.employee_code + # Solo admin puede cambiar roles if user_update.role is not None: if current_user.role != "admin": @@ -1519,9 +1523,9 @@ def delete_ai_configuration( @app.post("/api/analyze-image") async def analyze_image( file: UploadFile = File(...), - question_id: int = None, - inspection_id: int = None, - custom_prompt: str = None, + question_id: int = Form(None), + inspection_id: int = Form(None), + custom_prompt: str = Form(None), db: Session = Depends(get_db), current_user: models.User = Depends(get_current_user) ): @@ -1530,6 +1534,15 @@ async def analyze_image( Usa la configuración de IA activa (OpenAI o Gemini) Incluye contexto del vehículo si se proporciona inspection_id """ + print("\n" + "="*80) + print("🔍 ANALYZE IMAGE - DEBUG") + print("="*80) + print(f"📥 Parámetros recibidos:") + print(f" - file: {file.filename}") + print(f" - question_id: {question_id}") + print(f" - inspection_id: {inspection_id}") + print(f" - custom_prompt (del Form): {custom_prompt[:100] if custom_prompt else 'NO RECIBIDO'}") + # Obtener configuración de IA activa ai_config = db.query(models.AIConfiguration).filter( models.AIConfiguration.is_active == True @@ -1551,71 +1564,71 @@ async def analyze_image( question_obj = None if question_id: question_obj = db.query(models.Question).filter(models.Question.id == question_id).first() + print(f"📋 Pregunta encontrada:") + print(f" - ID: {question_obj.id}") + print(f" - Texto: {question_obj.text}") + print(f" - ai_prompt en DB: {question_obj.ai_prompt[:100] if question_obj.ai_prompt else 'NO TIENE'}") + + # Si no se proporciona custom_prompt en el Form, usar el de la pregunta + if not custom_prompt and question_obj and question_obj.ai_prompt: + custom_prompt = question_obj.ai_prompt + print(f"✅ Usando ai_prompt de la pregunta de la DB") + elif custom_prompt: + print(f"✅ Usando custom_prompt del Form") + else: + print(f"⚠️ NO HAY custom_prompt (ni del Form ni de la DB)") + + print(f"📝 Custom prompt FINAL a usar: {custom_prompt[:150] if custom_prompt else 'NINGUNO'}...") # Obtener contexto del vehículo si se proporciona inspection_id vehicle_context = "" if inspection_id: inspection = db.query(models.Inspection).filter(models.Inspection.id == inspection_id).first() if inspection: + print(f"🚗 Contexto del vehículo agregado: {inspection.vehicle_brand} {inspection.vehicle_model}") vehicle_context = f""" INFORMACIÓN DEL VEHÍCULO INSPECCIONADO: - Marca: {inspection.vehicle_brand} - Modelo: {inspection.vehicle_model} -- Año: {inspection.vehicle_year or 'No especificado'} - Placa: {inspection.vehicle_plate} -- Kilometraje: {inspection.mileage} km +- Kilometraje: {inspection.vehicle_km} km - Cliente: {inspection.client_name} - OR/Orden: {inspection.or_number} """ + else: + print(f"⚠️ inspection_id {inspection_id} no encontrado en DB") + else: + print(f"⚠️ NO se proporcionó inspection_id, sin contexto de vehículo") try: # Construir prompt dinámico basado en la pregunta específica if question_obj: # Usar prompt personalizado si está disponible if custom_prompt: - # Prompt 100% personalizado por el administrador + # Prompt personalizado - DIRECTO Y SIMPLE system_prompt = f"""Eres un mecánico experto realizando una inspección vehicular. {vehicle_context} -INSTRUCCIONES ESPECÍFICAS DEL ADMINISTRADOR PARA ESTA PREGUNTA: +TAREA ESPECÍFICA: {custom_prompt} -PREGUNTA A RESPONDER: "{question_obj.text}" -Sección: {question_obj.section} - -IMPORTANTE - VALIDACIÓN ESTRICTA: -1. Lee CUIDADOSAMENTE las instrucciones específicas del administrador arriba -2. Verifica si la imagen proporcionada PERMITE responder lo que se pide -3. Si las instrucciones piden verificar algo dinámico (como "si prende", "si funciona", "si enciende"): - - Y la imagen es estática (foto), indica en "recommendation" que NO se puede verificar con una foto estática - - Sugiere que se necesita una prueba en vivo o un video -4. Si la imagen NO corresponde a lo que piden las instrucciones, indica claramente en "recommendation" qué foto necesitan tomar - -VALIDACIÓN DE IMAGEN: -- Si piden verificar funcionamiento (prende, enciende, funciona) pero solo hay una foto → Indica "No se puede verificar funcionamiento con foto estática. Se requiere prueba en vivo." -- Si la imagen es borrosa o no permite análisis → Indica que tomen otra foto más clara -- Si la imagen muestra un componente diferente al solicitado → Indica qué foto necesitan tomar - Responde SOLO en formato JSON válido (sin markdown, sin ```json): {{ - "status": "minor", - "observations": "Describe lo que SÍ puedes ver en la imagen y explica por qué no puedes responder completamente la pregunta si aplica", - "recommendation": "Si no puedes verificar lo solicitado con la imagen, explica claramente QUÉ se necesita (prueba en vivo, video, foto diferente, etc.)", + "status": "ok", + "observations": "Describe lo que observas en la imagen en relación a la tarea solicitada", + "recommendation": "Acción sugerida basada en lo observado", "confidence": 0.85 }} VALORES DE STATUS: -- "ok": Solo si puedes CONFIRMAR que todo está bien según las instrucciones -- "minor": Si hay limitaciones en la imagen o no puedes verificar completamente lo solicitado -- "critical": Si hay problemas graves visibles o la imagen es completamente inadecuada +- "ok": Cumple con lo esperado según la tarea +- "minor": Presenta observaciones menores o advertencias +- "critical": Presenta problemas graves o no cumple con lo esperado -RECORDATORIO: En tus observaciones, menciona si el estado es apropiado para el kilometraje y marca/modelo del vehículo cuando sea relevante.""" +IMPORTANTE: Si la tarea requiere verificar funcionamiento (algo encendido, prendido, activo) pero la imagen muestra el componente apagado o en reposo, usa status "critical" e indica en "recommendation" que se necesita una foto con el componente funcionando o un video.""" - if vehicle_context: - user_message = f"Inspecciona esta imagen del vehículo. Las instrucciones específicas requieren: '{custom_prompt}'. Verifica si con esta imagen puedes responder completamente esa solicitud." - else: - user_message = f"Inspecciona la imagen. Las instrucciones requieren: '{custom_prompt}'. Verifica si puedes responder esa solicitud con esta imagen." + user_message = f"Pregunta de inspección: {question_obj.text}\n\nAnaliza esta imagen según la tarea especificada." else: # Prompt altamente específico para la pregunta question_text = question_obj.text @@ -1682,6 +1695,13 @@ Responde SOLO en formato JSON válido (sin markdown, sin ```json): NOTA: "status" debe ser "ok" (bueno), "minor" (problemas leves) o "critical" (problemas graves).""" user_message = "Analiza este componente del vehículo para la inspección general." + print(f"\n🤖 PROMPT ENVIADO AL AI:") + print(f"Provider: {ai_config.provider}") + print(f"Model: {ai_config.model_name}") + print(f"System prompt (primeros 200 chars): {system_prompt[:200]}...") + print(f"User message: {user_message}") + print("="*80 + "\n") + if ai_config.provider == "openai": import openai openai.api_key = ai_config.api_key diff --git a/backend/app/models.py b/backend/app/models.py index 6379462..059cb66 100644 --- a/backend/app/models.py +++ b/backend/app/models.py @@ -12,6 +12,7 @@ class User(Base): password_hash = Column(String(255), nullable=False) role = Column(String(20), nullable=False) # admin, mechanic, asesor full_name = Column(String(100)) + employee_code = Column(String(50)) # Nro Operario - código de otro sistema is_active = Column(Boolean, default=True) created_at = Column(DateTime(timezone=True), server_default=func.now()) diff --git a/backend/app/schemas.py b/backend/app/schemas.py index 2f924ed..504ccc4 100644 --- a/backend/app/schemas.py +++ b/backend/app/schemas.py @@ -7,6 +7,7 @@ class UserBase(BaseModel): username: str email: Optional[EmailStr] = None full_name: Optional[str] = None + employee_code: Optional[str] = None # Nro Operario - código de otro sistema role: str = "mechanic" class UserCreate(UserBase): @@ -16,6 +17,7 @@ class UserUpdate(BaseModel): username: Optional[str] = None email: Optional[EmailStr] = None full_name: Optional[str] = None + employee_code: Optional[str] = None role: Optional[str] = None class UserPasswordUpdate(BaseModel): @@ -31,6 +33,7 @@ class UserLogin(BaseModel): class User(UserBase): id: int + employee_code: Optional[str] = None is_active: bool created_at: datetime diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 5661136..19181d7 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -3088,14 +3088,25 @@ function InspectionModal({ checklist, user, onClose, onComplete }) { formData.append('file', file) formData.append('question_id', question.id.toString()) + console.log('📤 DATOS ENVIADOS AL BACKEND:') + console.log(' - question_id:', question.id) + console.log(' - question.text:', question.text) + console.log(' - question.ai_prompt:', question.ai_prompt || 'NO TIENE') + // Include inspection_id for vehicle context if (inspectionId) { formData.append('inspection_id', inspectionId.toString()) + console.log(' - inspection_id:', inspectionId) + } else { + console.log(' - inspection_id: NO ENVIADO') } // Include custom prompt if available if (question.ai_prompt) { formData.append('custom_prompt', question.ai_prompt) + console.log(' - custom_prompt ENVIADO:', question.ai_prompt) + } else { + console.log(' - custom_prompt: NO ENVIADO (pregunta no tiene ai_prompt)') } const response = await fetch(`${API_URL}/api/analyze-image`, { @@ -3104,13 +3115,21 @@ function InspectionModal({ checklist, user, onClose, onComplete }) { body: formData }) + console.log('📥 RESPUESTA DEL BACKEND:') + console.log(' - Status:', response.status) + if (response.ok) { const result = await response.json() + console.log(' - Result completo:', JSON.stringify(result, null, 2)) // Check if AI analysis was successful if (result.success && result.analysis) { analyses.push(result) - console.log('✅ Análisis IA:', result) + console.log('✅ Análisis IA exitoso') + console.log(' - Provider:', result.provider) + console.log(' - Model:', result.model) + console.log(' - Status:', result.analysis.status) + console.log(' - Observations:', result.analysis.observations) } else { console.warn('⚠️ Error en análisis IA:', result.error || result.message) // Show user-friendly error @@ -3119,7 +3138,8 @@ function InspectionModal({ checklist, user, onClose, onComplete }) { } } } else { - console.warn('⚠️ Error HTTP en análisis IA:', response.status, await response.text()) + const errorText = await response.text() + console.warn('⚠️ Error HTTP en análisis IA:', response.status, errorText) } } @@ -3703,6 +3723,7 @@ function UsersTab({ user }) { username: '', email: '', password: '', + employee_code: '', role: 'mechanic' }) @@ -3746,7 +3767,7 @@ function UsersTab({ user }) { if (response.ok) { setShowCreateForm(false) - setFormData({ username: '', email: '', password: '', role: 'mechanic' }) + setFormData({ username: '', email: '', password: '', employee_code: '', role: 'mechanic' }) loadUsers() } else { const error = await response.json() @@ -3872,6 +3893,9 @@ function UsersTab({ user }) {

{u.username}

{u.email}

+ {u.employee_code && ( +

Nro Operario: {u.employee_code}

+ )}
+
+ + setFormData({...formData, employee_code: e.target.value})} + className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-transparent" + placeholder="Código de operario" + /> +
+
+
+ + setEditingUser({...editingUser, employee_code: e.target.value})} + className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-transparent" + placeholder="Código de operario" + /> +
+