Backend v1.0.73:

- Implementado sistema de reordenamiento de preguntas
- Nuevo endpoint PATCH /api/checklists/{id}/questions/reorder
- Schema QuestionReorder para validar datos de reorden
- Actualización en lote de campo 'order' en preguntas
- Auditoría automática de cambios de orden
- Validación de permisos y existencia de checklist

Frontend v1.0.71:
- Agregada funcionalidad de reordenamiento de preguntas
- Botones ▲ ▼ para mover preguntas arriba/abajo
- Función moveQuestion() para gestionar reordenamiento
- Interfaz visual mejorada con separadores
- Tooltips descriptivos en botones de orden
- Recarga automática tras reordenar
This commit is contained in:
2025-11-27 16:15:20 -03:00
parent 826c5fce5e
commit 651aa138cf
4 changed files with 116 additions and 3 deletions

View File

@@ -204,7 +204,7 @@ def send_completed_inspection_to_n8n(inspection, db):
# No lanzamos excepción para no interrumpir el flujo normal # No lanzamos excepción para no interrumpir el flujo normal
BACKEND_VERSION = "1.0.72" BACKEND_VERSION = "1.0.73"
app = FastAPI(title="Checklist Inteligente API", version=BACKEND_VERSION) app = FastAPI(title="Checklist Inteligente API", version=BACKEND_VERSION)
# S3/MinIO configuration # S3/MinIO configuration
@@ -1041,6 +1041,52 @@ def update_question(
return db_question return db_question
@app.patch("/api/checklists/{checklist_id}/questions/reorder")
def reorder_questions(
checklist_id: int,
reorder_data: List[schemas.QuestionReorder],
db: Session = Depends(get_db),
current_user: models.User = Depends(get_current_user)
):
"""Reordenar preguntas de un checklist"""
if current_user.role != "admin":
raise HTTPException(status_code=403, detail="No autorizado")
# Verificar que el checklist existe
checklist = db.query(models.Checklist).filter(models.Checklist.id == checklist_id).first()
if not checklist:
raise HTTPException(status_code=404, detail="Checklist no encontrado")
# Actualizar el orden de cada pregunta
for item in reorder_data:
question = db.query(models.Question).filter(
models.Question.id == item.question_id,
models.Question.checklist_id == checklist_id
).first()
if question:
old_order = question.order
question.order = item.new_order
question.updated_at = datetime.utcnow()
# Registrar auditoría
audit_log = models.QuestionAuditLog(
question_id=question.id,
checklist_id=checklist_id,
user_id=current_user.id,
action="updated",
field_name="order",
old_value=str(old_order),
new_value=str(item.new_order),
comment="Orden de pregunta actualizado"
)
db.add(audit_log)
db.commit()
return {"message": "Orden de preguntas actualizado exitosamente", "updated_count": len(reorder_data)}
@app.delete("/api/questions/{question_id}") @app.delete("/api/questions/{question_id}")
def delete_question( def delete_question(
question_id: int, question_id: int,

View File

@@ -134,6 +134,10 @@ class QuestionCreate(QuestionBase):
class QuestionUpdate(QuestionBase): class QuestionUpdate(QuestionBase):
pass pass
class QuestionReorder(BaseModel):
question_id: int
new_order: int
class Question(QuestionBase): class Question(QuestionBase):
id: int id: int
checklist_id: int checklist_id: int

View File

@@ -1,7 +1,7 @@
{ {
"name": "checklist-frontend", "name": "checklist-frontend",
"private": true, "private": true,
"version": "1.0.70", "version": "1.0.71",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",

View File

@@ -1167,6 +1167,51 @@ function QuestionsManagerModal({ checklist, onClose }) {
} }
} }
const moveQuestion = async (questionId, direction) => {
const questionsList = Object.values(questionsBySection).flat()
const currentIndex = questionsList.findIndex(q => q.id === questionId)
if (currentIndex === -1) return
const newIndex = direction === 'up' ? currentIndex - 1 : currentIndex + 1
if (newIndex < 0 || newIndex >= questionsList.length) return
// Crear nueva lista con el orden actualizado
const newList = [...questionsList]
const [movedQuestion] = newList.splice(currentIndex, 1)
newList.splice(newIndex, 0, movedQuestion)
// Preparar datos para el backend
const reorderData = newList.map((q, index) => ({
question_id: q.id,
new_order: index
}))
try {
const token = localStorage.getItem('token')
const API_URL = import.meta.env.VITE_API_URL || ''
const response = await fetch(`${API_URL}/api/checklists/${checklist.id}/questions/reorder`, {
method: 'PATCH',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(reorderData)
})
if (response.ok) {
loadQuestions()
} else {
alert('Error al reordenar pregunta')
}
} catch (error) {
console.error('Error:', error)
alert('Error al reordenar pregunta')
}
}
const questionsBySection = questions.reduce((acc, q) => { const questionsBySection = questions.reduce((acc, q) => {
const section = q.section || 'Sin sección' const section = q.section || 'Sin sección'
if (!acc[section]) acc[section] = [] if (!acc[section]) acc[section] = []
@@ -1582,7 +1627,25 @@ function QuestionsManagerModal({ checklist, onClose }) {
</div> </div>
</div> </div>
</div> </div>
<div className="ml-4 flex gap-2"> <div className="ml-4 flex gap-2 items-center">
{/* Botones de reordenamiento */}
<div className="flex flex-col gap-1">
<button
onClick={() => moveQuestion(question.id, 'up')}
className="text-gray-500 hover:text-indigo-600 text-xs px-2 py-1 hover:bg-indigo-50 rounded transition"
title="Mover arriba"
>
</button>
<button
onClick={() => moveQuestion(question.id, 'down')}
className="text-gray-500 hover:text-indigo-600 text-xs px-2 py-1 hover:bg-indigo-50 rounded transition"
title="Mover abajo"
>
</button>
</div>
<div className="h-8 w-px bg-gray-300"></div>
<button <button
onClick={() => loadAuditHistory(question.id)} onClick={() => loadAuditHistory(question.id)}
className="text-gray-600 hover:text-gray-700 text-sm" className="text-gray-600 hover:text-gray-700 text-sm"