Actualziacion de analisis de ia con las imagenes y se agrega el campo de cod operario en el front y en el back

This commit is contained in:
2025-11-26 01:20:26 -03:00
parent cbfab59222
commit 822ab5a1cb
5 changed files with 129 additions and 38 deletions

View File

@@ -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

View File

@@ -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())

View File

@@ -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

View File

@@ -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 }) {
<div>
<h3 className="font-semibold text-gray-800">{u.username}</h3>
<p className="text-sm text-gray-500">{u.email}</p>
{u.employee_code && (
<p className="text-xs text-gray-400 mt-0.5">Nro Operario: {u.employee_code}</p>
)}
<div className="flex gap-2 mt-1">
<span className={`text-xs px-2 py-1 rounded ${
u.role === 'admin'
@@ -3960,6 +3984,19 @@ function UsersTab({ user }) {
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Nro Operario <span className="text-gray-400 text-xs">(opcional)</span>
</label>
<input
type="text"
value={formData.employee_code || ''}
onChange={(e) => 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"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Contraseña
@@ -3993,7 +4030,7 @@ function UsersTab({ user }) {
type="button"
onClick={() => {
setShowCreateForm(false)
setFormData({ username: '', email: '', password: '', role: 'mechanic' })
setFormData({ username: '', email: '', password: '', employee_code: '', role: 'mechanic' })
}}
className="flex-1 px-4 py-2 border border-gray-300 text-gray-700 rounded-lg hover:bg-gray-50 transition"
>
@@ -4021,6 +4058,7 @@ function UsersTab({ user }) {
const updates = {
username: editingUser.username,
email: editingUser.email,
employee_code: editingUser.employee_code,
role: editingUser.role
}
handleUpdateUser(editingUser.id, updates)
@@ -4051,6 +4089,19 @@ function UsersTab({ user }) {
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Nro Operario <span className="text-gray-400 text-xs">(opcional)</span>
</label>
<input
type="text"
value={editingUser.employee_code || ''}
onChange={(e) => 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"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Rol

View File

@@ -0,0 +1,16 @@
-- Migración: Agregar campo employee_code (Nro Operario) a la tabla users
-- Fecha: 2025-11-26
-- Descripción: Agrega un campo opcional para almacenar el código de operario de otro sistema
-- Agregar columna employee_code a la tabla users
ALTER TABLE users
ADD COLUMN IF NOT EXISTS employee_code VARCHAR(50);
-- Comentario descriptivo
COMMENT ON COLUMN users.employee_code IS 'Número de operario - código de identificación de otro sistema';
-- Verificar que la columna se agregó correctamente
SELECT column_name, data_type, character_maximum_length
FROM information_schema.columns
WHERE table_name = 'users'
AND column_name = 'employee_code';