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:
@@ -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
|
||||
|
||||
@@ -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())
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
16
migrations/add_employee_code_to_users.sql
Normal file
16
migrations/add_employee_code_to_users.sql
Normal 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';
|
||||
Reference in New Issue
Block a user