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,
|
username=user.username,
|
||||||
email=user.email,
|
email=user.email,
|
||||||
full_name=user.full_name,
|
full_name=user.full_name,
|
||||||
|
employee_code=user.employee_code,
|
||||||
role=user.role,
|
role=user.role,
|
||||||
password_hash=hashed_password,
|
password_hash=hashed_password,
|
||||||
is_active=True
|
is_active=True
|
||||||
@@ -374,6 +375,9 @@ def update_user(
|
|||||||
if user_update.full_name is not None:
|
if user_update.full_name is not None:
|
||||||
db_user.full_name = user_update.full_name
|
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
|
# Solo admin puede cambiar roles
|
||||||
if user_update.role is not None:
|
if user_update.role is not None:
|
||||||
if current_user.role != "admin":
|
if current_user.role != "admin":
|
||||||
@@ -1519,9 +1523,9 @@ def delete_ai_configuration(
|
|||||||
@app.post("/api/analyze-image")
|
@app.post("/api/analyze-image")
|
||||||
async def analyze_image(
|
async def analyze_image(
|
||||||
file: UploadFile = File(...),
|
file: UploadFile = File(...),
|
||||||
question_id: int = None,
|
question_id: int = Form(None),
|
||||||
inspection_id: int = None,
|
inspection_id: int = Form(None),
|
||||||
custom_prompt: str = None,
|
custom_prompt: str = Form(None),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
current_user: models.User = Depends(get_current_user)
|
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)
|
Usa la configuración de IA activa (OpenAI o Gemini)
|
||||||
Incluye contexto del vehículo si se proporciona inspection_id
|
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
|
# Obtener configuración de IA activa
|
||||||
ai_config = db.query(models.AIConfiguration).filter(
|
ai_config = db.query(models.AIConfiguration).filter(
|
||||||
models.AIConfiguration.is_active == True
|
models.AIConfiguration.is_active == True
|
||||||
@@ -1551,71 +1564,71 @@ async def analyze_image(
|
|||||||
question_obj = None
|
question_obj = None
|
||||||
if question_id:
|
if question_id:
|
||||||
question_obj = db.query(models.Question).filter(models.Question.id == question_id).first()
|
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
|
# Obtener contexto del vehículo si se proporciona inspection_id
|
||||||
vehicle_context = ""
|
vehicle_context = ""
|
||||||
if inspection_id:
|
if inspection_id:
|
||||||
inspection = db.query(models.Inspection).filter(models.Inspection.id == inspection_id).first()
|
inspection = db.query(models.Inspection).filter(models.Inspection.id == inspection_id).first()
|
||||||
if inspection:
|
if inspection:
|
||||||
|
print(f"🚗 Contexto del vehículo agregado: {inspection.vehicle_brand} {inspection.vehicle_model}")
|
||||||
vehicle_context = f"""
|
vehicle_context = f"""
|
||||||
INFORMACIÓN DEL VEHÍCULO INSPECCIONADO:
|
INFORMACIÓN DEL VEHÍCULO INSPECCIONADO:
|
||||||
- Marca: {inspection.vehicle_brand}
|
- Marca: {inspection.vehicle_brand}
|
||||||
- Modelo: {inspection.vehicle_model}
|
- Modelo: {inspection.vehicle_model}
|
||||||
- Año: {inspection.vehicle_year or 'No especificado'}
|
|
||||||
- Placa: {inspection.vehicle_plate}
|
- Placa: {inspection.vehicle_plate}
|
||||||
- Kilometraje: {inspection.mileage} km
|
- Kilometraje: {inspection.vehicle_km} km
|
||||||
- Cliente: {inspection.client_name}
|
- Cliente: {inspection.client_name}
|
||||||
- OR/Orden: {inspection.or_number}
|
- 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:
|
try:
|
||||||
# Construir prompt dinámico basado en la pregunta específica
|
# Construir prompt dinámico basado en la pregunta específica
|
||||||
if question_obj:
|
if question_obj:
|
||||||
# Usar prompt personalizado si está disponible
|
# Usar prompt personalizado si está disponible
|
||||||
if custom_prompt:
|
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.
|
system_prompt = f"""Eres un mecánico experto realizando una inspección vehicular.
|
||||||
|
|
||||||
{vehicle_context}
|
{vehicle_context}
|
||||||
|
|
||||||
INSTRUCCIONES ESPECÍFICAS DEL ADMINISTRADOR PARA ESTA PREGUNTA:
|
TAREA ESPECÍFICA:
|
||||||
{custom_prompt}
|
{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):
|
Responde SOLO en formato JSON válido (sin markdown, sin ```json):
|
||||||
{{
|
{{
|
||||||
"status": "minor",
|
"status": "ok",
|
||||||
"observations": "Describe lo que SÍ puedes ver en la imagen y explica por qué no puedes responder completamente la pregunta si aplica",
|
"observations": "Describe lo que observas en la imagen en relación a la tarea solicitada",
|
||||||
"recommendation": "Si no puedes verificar lo solicitado con la imagen, explica claramente QUÉ se necesita (prueba en vivo, video, foto diferente, etc.)",
|
"recommendation": "Acción sugerida basada en lo observado",
|
||||||
"confidence": 0.85
|
"confidence": 0.85
|
||||||
}}
|
}}
|
||||||
|
|
||||||
VALORES DE STATUS:
|
VALORES DE STATUS:
|
||||||
- "ok": Solo si puedes CONFIRMAR que todo está bien según las instrucciones
|
- "ok": Cumple con lo esperado según la tarea
|
||||||
- "minor": Si hay limitaciones en la imagen o no puedes verificar completamente lo solicitado
|
- "minor": Presenta observaciones menores o advertencias
|
||||||
- "critical": Si hay problemas graves visibles o la imagen es completamente inadecuada
|
- "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"Pregunta de inspección: {question_obj.text}\n\nAnaliza esta imagen según la tarea especificada."
|
||||||
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."
|
|
||||||
else:
|
else:
|
||||||
# Prompt altamente específico para la pregunta
|
# Prompt altamente específico para la pregunta
|
||||||
question_text = question_obj.text
|
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)."""
|
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."
|
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":
|
if ai_config.provider == "openai":
|
||||||
import openai
|
import openai
|
||||||
openai.api_key = ai_config.api_key
|
openai.api_key = ai_config.api_key
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ class User(Base):
|
|||||||
password_hash = Column(String(255), nullable=False)
|
password_hash = Column(String(255), nullable=False)
|
||||||
role = Column(String(20), nullable=False) # admin, mechanic, asesor
|
role = Column(String(20), nullable=False) # admin, mechanic, asesor
|
||||||
full_name = Column(String(100))
|
full_name = Column(String(100))
|
||||||
|
employee_code = Column(String(50)) # Nro Operario - código de otro sistema
|
||||||
is_active = Column(Boolean, default=True)
|
is_active = Column(Boolean, default=True)
|
||||||
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ class UserBase(BaseModel):
|
|||||||
username: str
|
username: str
|
||||||
email: Optional[EmailStr] = None
|
email: Optional[EmailStr] = None
|
||||||
full_name: Optional[str] = None
|
full_name: Optional[str] = None
|
||||||
|
employee_code: Optional[str] = None # Nro Operario - código de otro sistema
|
||||||
role: str = "mechanic"
|
role: str = "mechanic"
|
||||||
|
|
||||||
class UserCreate(UserBase):
|
class UserCreate(UserBase):
|
||||||
@@ -16,6 +17,7 @@ class UserUpdate(BaseModel):
|
|||||||
username: Optional[str] = None
|
username: Optional[str] = None
|
||||||
email: Optional[EmailStr] = None
|
email: Optional[EmailStr] = None
|
||||||
full_name: Optional[str] = None
|
full_name: Optional[str] = None
|
||||||
|
employee_code: Optional[str] = None
|
||||||
role: Optional[str] = None
|
role: Optional[str] = None
|
||||||
|
|
||||||
class UserPasswordUpdate(BaseModel):
|
class UserPasswordUpdate(BaseModel):
|
||||||
@@ -31,6 +33,7 @@ class UserLogin(BaseModel):
|
|||||||
|
|
||||||
class User(UserBase):
|
class User(UserBase):
|
||||||
id: int
|
id: int
|
||||||
|
employee_code: Optional[str] = None
|
||||||
is_active: bool
|
is_active: bool
|
||||||
created_at: datetime
|
created_at: datetime
|
||||||
|
|
||||||
|
|||||||
@@ -3088,14 +3088,25 @@ function InspectionModal({ checklist, user, onClose, onComplete }) {
|
|||||||
formData.append('file', file)
|
formData.append('file', file)
|
||||||
formData.append('question_id', question.id.toString())
|
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
|
// Include inspection_id for vehicle context
|
||||||
if (inspectionId) {
|
if (inspectionId) {
|
||||||
formData.append('inspection_id', inspectionId.toString())
|
formData.append('inspection_id', inspectionId.toString())
|
||||||
|
console.log(' - inspection_id:', inspectionId)
|
||||||
|
} else {
|
||||||
|
console.log(' - inspection_id: NO ENVIADO')
|
||||||
}
|
}
|
||||||
|
|
||||||
// Include custom prompt if available
|
// Include custom prompt if available
|
||||||
if (question.ai_prompt) {
|
if (question.ai_prompt) {
|
||||||
formData.append('custom_prompt', 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`, {
|
const response = await fetch(`${API_URL}/api/analyze-image`, {
|
||||||
@@ -3104,13 +3115,21 @@ function InspectionModal({ checklist, user, onClose, onComplete }) {
|
|||||||
body: formData
|
body: formData
|
||||||
})
|
})
|
||||||
|
|
||||||
|
console.log('📥 RESPUESTA DEL BACKEND:')
|
||||||
|
console.log(' - Status:', response.status)
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
const result = await response.json()
|
const result = await response.json()
|
||||||
|
console.log(' - Result completo:', JSON.stringify(result, null, 2))
|
||||||
|
|
||||||
// Check if AI analysis was successful
|
// Check if AI analysis was successful
|
||||||
if (result.success && result.analysis) {
|
if (result.success && result.analysis) {
|
||||||
analyses.push(result)
|
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 {
|
} else {
|
||||||
console.warn('⚠️ Error en análisis IA:', result.error || result.message)
|
console.warn('⚠️ Error en análisis IA:', result.error || result.message)
|
||||||
// Show user-friendly error
|
// Show user-friendly error
|
||||||
@@ -3119,7 +3138,8 @@ function InspectionModal({ checklist, user, onClose, onComplete }) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} 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: '',
|
username: '',
|
||||||
email: '',
|
email: '',
|
||||||
password: '',
|
password: '',
|
||||||
|
employee_code: '',
|
||||||
role: 'mechanic'
|
role: 'mechanic'
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -3746,7 +3767,7 @@ function UsersTab({ user }) {
|
|||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
setShowCreateForm(false)
|
setShowCreateForm(false)
|
||||||
setFormData({ username: '', email: '', password: '', role: 'mechanic' })
|
setFormData({ username: '', email: '', password: '', employee_code: '', role: 'mechanic' })
|
||||||
loadUsers()
|
loadUsers()
|
||||||
} else {
|
} else {
|
||||||
const error = await response.json()
|
const error = await response.json()
|
||||||
@@ -3872,6 +3893,9 @@ function UsersTab({ user }) {
|
|||||||
<div>
|
<div>
|
||||||
<h3 className="font-semibold text-gray-800">{u.username}</h3>
|
<h3 className="font-semibold text-gray-800">{u.username}</h3>
|
||||||
<p className="text-sm text-gray-500">{u.email}</p>
|
<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">
|
<div className="flex gap-2 mt-1">
|
||||||
<span className={`text-xs px-2 py-1 rounded ${
|
<span className={`text-xs px-2 py-1 rounded ${
|
||||||
u.role === 'admin'
|
u.role === 'admin'
|
||||||
@@ -3960,6 +3984,19 @@ function UsersTab({ user }) {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</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>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||||
Contraseña
|
Contraseña
|
||||||
@@ -3993,7 +4030,7 @@ function UsersTab({ user }) {
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setShowCreateForm(false)
|
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"
|
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 = {
|
const updates = {
|
||||||
username: editingUser.username,
|
username: editingUser.username,
|
||||||
email: editingUser.email,
|
email: editingUser.email,
|
||||||
|
employee_code: editingUser.employee_code,
|
||||||
role: editingUser.role
|
role: editingUser.role
|
||||||
}
|
}
|
||||||
handleUpdateUser(editingUser.id, updates)
|
handleUpdateUser(editingUser.id, updates)
|
||||||
@@ -4051,6 +4089,19 @@ function UsersTab({ user }) {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</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>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||||
Rol
|
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