From ed3f513075728fc7b278804b931f404ce11147a7 Mon Sep 17 00:00:00 2001 From: gitea Date: Thu, 27 Nov 2025 15:32:56 -0300 Subject: [PATCH] =?UTF-8?q?Backend=20v1.0.71:=20-=20Implementado=20soft=20?= =?UTF-8?q?delete=20para=20preguntas=20-=20Nuevas=20columnas:=20is=5Fdelet?= =?UTF-8?q?ed=20(boolean),=20updated=5Fat=20(timestamp)=20-=20Migraci?= =?UTF-8?q?=C3=B3n=20SQL:=20add=5Fsoft=5Fdelete=5Fto=5Fquestions.sql=20-?= =?UTF-8?q?=20Endpoint=20DELETE=20marca=20preguntas=20como=20eliminadas=20?= =?UTF-8?q?en=20lugar=20de=20borrarlas=20-=20GET=20/api/checklists/{id}=20?= =?UTF-8?q?filtra=20preguntas=20eliminadas=20(is=5Fdeleted=3Dfalse)=20-=20?= =?UTF-8?q?Validaci=C3=B3n=20de=20subpreguntas=20activas=20antes=20de=20el?= =?UTF-8?q?iminar=20-=20=C3=8Dndices=20agregados=20para=20optimizar=20quer?= =?UTF-8?q?ies=20-=20Mantiene=20integridad=20de=20respuestas=20hist=C3=B3r?= =?UTF-8?q?icas=20y=20PDFs=20generados=20-=20Permite=20limpiar=20checklist?= =?UTF-8?q?s=20sin=20afectar=20inspecciones=20completadas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/copilot-instructions.md | 1 + backend/app/main.py | 38 +++++++++++++++++---- backend/app/models.py | 4 +++ backend/app/schemas.py | 2 ++ frontend/package.json | 2 +- frontend/src/App.jsx | 14 +++++--- migrations/add_soft_delete_to_questions.sql | 25 ++++++++++++++ 7 files changed, 75 insertions(+), 11 deletions(-) create mode 100644 migrations/add_soft_delete_to_questions.sql diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index e29572b..49f431a 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -1,5 +1,6 @@ # No olvides dar lo comentarios de los cambios que se hicieron para el backend y el front en para los comentarios de git # Siempre actuliza la version del front y del back la version del front esta en el archivo package.json y la del backend en el archivo main.py en una variable llamada BACKEND_VERSION +# Si el FrontEnd no sufre modificaciones no es necesario actualizar su version, al igual que el backend, solo poner en el comentario de git que no se hicieron cambios en el front o en el backend segun sea el caso # Ayudetec - Intelligent Checklist System for Automotive Workshops diff --git a/backend/app/main.py b/backend/app/main.py index 8b28020..59205cb 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -204,7 +204,7 @@ def send_completed_inspection_to_n8n(inspection, db): # No lanzamos excepción para no interrumpir el flujo normal -BACKEND_VERSION = "1.0.69" +BACKEND_VERSION = "1.0.71" app = FastAPI(title="Checklist Inteligente API", version=BACKEND_VERSION) # S3/MinIO configuration @@ -780,13 +780,17 @@ def get_checklists( @app.get("/api/checklists/{checklist_id}", response_model=schemas.ChecklistWithQuestions) def get_checklist(checklist_id: int, db: Session = Depends(get_db)): - checklist = db.query(models.Checklist).options( - joinedload(models.Checklist.questions) - ).filter(models.Checklist.id == checklist_id).first() + checklist = db.query(models.Checklist).filter(models.Checklist.id == checklist_id).first() if not checklist: raise HTTPException(status_code=404, detail="Checklist no encontrado") + # Cargar solo preguntas NO eliminadas + checklist.questions = db.query(models.Question).filter( + models.Question.checklist_id == checklist_id, + models.Question.is_deleted == False + ).order_by(models.Question.order).all() + # Agregar allowed_mechanics permissions = db.query(models.ChecklistPermission.mechanic_id).filter( models.ChecklistPermission.checklist_id == checklist.id @@ -1050,6 +1054,21 @@ def delete_question( if not db_question: raise HTTPException(status_code=404, detail="Pregunta no encontrada") + if db_question.is_deleted: + raise HTTPException(status_code=400, detail="La pregunta ya está eliminada") + + # Verificar si tiene subpreguntas activas + active_subquestions = db.query(models.Question).filter( + models.Question.parent_question_id == question_id, + models.Question.is_deleted == False + ).count() + + if active_subquestions > 0: + raise HTTPException( + status_code=400, + detail=f"No se puede eliminar la pregunta porque tiene {active_subquestions} subpregunta(s) activa(s). Elimina primero las subpreguntas." + ) + # Registrar auditoría antes de eliminar audit_log = models.QuestionAuditLog( question_id=question_id, @@ -1061,9 +1080,16 @@ def delete_question( ) db.add(audit_log) - db.delete(db_question) + # SOFT DELETE: marcar como eliminada en lugar de borrar físicamente + db_question.is_deleted = True + db_question.updated_at = datetime.utcnow() + db.commit() - return {"message": "Pregunta eliminada"} + + return { + "message": "Pregunta eliminada exitosamente", + "note": "Las respuestas históricas se mantienen intactas. La pregunta no aparecerá en nuevas inspecciones." + } @app.get("/api/questions/{question_id}/audit", response_model=List[schemas.QuestionAuditLog]) diff --git a/backend/app/models.py b/backend/app/models.py index ea1ef42..d3bde12 100644 --- a/backend/app/models.py +++ b/backend/app/models.py @@ -83,7 +83,11 @@ class Question(Base): # AI Analysis ai_prompt = Column(Text, nullable=True) # Prompt personalizado para análisis de IA de esta pregunta + # Soft Delete + is_deleted = Column(Boolean, default=False) # Soft delete: mantiene integridad de respuestas históricas + created_at = Column(DateTime(timezone=True), server_default=func.now()) + updated_at = Column(DateTime(timezone=True), onupdate=func.now()) # Relationships checklist = relationship("Checklist", back_populates="questions") diff --git a/backend/app/schemas.py b/backend/app/schemas.py index 8d0934e..f4b55e5 100644 --- a/backend/app/schemas.py +++ b/backend/app/schemas.py @@ -126,6 +126,7 @@ class QuestionBase(BaseModel): parent_question_id: Optional[int] = None show_if_answer: Optional[str] = None ai_prompt: Optional[str] = None + is_deleted: bool = False class QuestionCreate(QuestionBase): checklist_id: int @@ -137,6 +138,7 @@ class Question(QuestionBase): id: int checklist_id: int created_at: datetime + updated_at: Optional[datetime] = None class Config: from_attributes = True diff --git a/frontend/package.json b/frontend/package.json index 2245dd6..e68a962 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,7 +1,7 @@ { "name": "checklist-frontend", "private": true, - "version": "1.0.69", + "version": "1.0.70", "type": "module", "scripts": { "dev": "vite", diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 3d28351..fb1a4d1 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -1050,8 +1050,6 @@ function QuestionsManagerModal({ checklist, onClose }) { } const handleEditQuestion = (question) => { - console.log('Editando pregunta:', question) - console.log('AI Prompt de la pregunta:', question.ai_prompt) setEditingQuestion(question) setShowCreateForm(false) setFormData({ @@ -1152,12 +1150,20 @@ function QuestionsManagerModal({ checklist, onClose }) { if (response.ok) { loadQuestions() + alert('✅ Pregunta eliminada exitosamente') } else { - alert('Error al eliminar pregunta') + const errorData = await response.json().catch(() => ({ detail: 'Error desconocido' })) + + if (response.status === 400) { + // Error de validación (pregunta con respuestas o subpreguntas) + alert(`⚠️ ${errorData.detail}`) + } else { + alert('❌ Error al eliminar pregunta') + } } } catch (error) { console.error('Error:', error) - alert('Error al eliminar pregunta') + alert('❌ Error de conexión al eliminar pregunta') } } diff --git a/migrations/add_soft_delete_to_questions.sql b/migrations/add_soft_delete_to_questions.sql new file mode 100644 index 0000000..acbf2f1 --- /dev/null +++ b/migrations/add_soft_delete_to_questions.sql @@ -0,0 +1,25 @@ +-- Migración: Agregar soft delete a preguntas +-- Fecha: 2025-11-27 +-- Descripción: Permite eliminar preguntas sin romper la integridad de respuestas históricas + +-- Agregar columna is_deleted a la tabla questions +ALTER TABLE questions ADD COLUMN IF NOT EXISTS is_deleted BOOLEAN DEFAULT FALSE; + +-- Agregar columna updated_at si no existe +ALTER TABLE questions ADD COLUMN IF NOT EXISTS updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(); + +-- Crear índice para mejorar queries que filtran por is_deleted +CREATE INDEX IF NOT EXISTS idx_questions_is_deleted ON questions(is_deleted); + +-- Crear índice compuesto para mejorar queries de preguntas activas por checklist +CREATE INDEX IF NOT EXISTS idx_questions_checklist_active ON questions(checklist_id, is_deleted); + +-- Actualizar preguntas existentes como no eliminadas +UPDATE questions SET is_deleted = FALSE WHERE is_deleted IS NULL; + +-- Actualizar updated_at en preguntas existentes +UPDATE questions SET updated_at = created_at WHERE updated_at IS NULL; + +-- Comentarios en las columnas +COMMENT ON COLUMN questions.is_deleted IS 'Soft delete: marca pregunta como eliminada sin borrarla físicamente, manteniendo integridad de respuestas históricas'; +COMMENT ON COLUMN questions.updated_at IS 'Timestamp de última actualización de la pregunta';