Backend v1.0.71:

- Implementado soft delete para preguntas
- Nuevas columnas: is_deleted (boolean), updated_at (timestamp)
- Migración SQL: add_soft_delete_to_questions.sql
- Endpoint DELETE marca preguntas como eliminadas en lugar de borrarlas
- GET /api/checklists/{id} filtra preguntas eliminadas (is_deleted=false)
- Validación de subpreguntas activas antes de eliminar
- Índices agregados para optimizar queries
- Mantiene integridad de respuestas históricas y PDFs generados
- Permite limpiar checklists sin afectar inspecciones completadas
This commit is contained in:
2025-11-27 15:32:56 -03:00
parent 027f22551c
commit ed3f513075
7 changed files with 75 additions and 11 deletions

View File

@@ -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 # 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 # 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 # Ayudetec - Intelligent Checklist System for Automotive Workshops

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.69" BACKEND_VERSION = "1.0.71"
app = FastAPI(title="Checklist Inteligente API", version=BACKEND_VERSION) app = FastAPI(title="Checklist Inteligente API", version=BACKEND_VERSION)
# S3/MinIO configuration # S3/MinIO configuration
@@ -780,13 +780,17 @@ def get_checklists(
@app.get("/api/checklists/{checklist_id}", response_model=schemas.ChecklistWithQuestions) @app.get("/api/checklists/{checklist_id}", response_model=schemas.ChecklistWithQuestions)
def get_checklist(checklist_id: int, db: Session = Depends(get_db)): def get_checklist(checklist_id: int, db: Session = Depends(get_db)):
checklist = db.query(models.Checklist).options( checklist = db.query(models.Checklist).filter(models.Checklist.id == checklist_id).first()
joinedload(models.Checklist.questions)
).filter(models.Checklist.id == checklist_id).first()
if not checklist: if not checklist:
raise HTTPException(status_code=404, detail="Checklist no encontrado") 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 # Agregar allowed_mechanics
permissions = db.query(models.ChecklistPermission.mechanic_id).filter( permissions = db.query(models.ChecklistPermission.mechanic_id).filter(
models.ChecklistPermission.checklist_id == checklist.id models.ChecklistPermission.checklist_id == checklist.id
@@ -1050,6 +1054,21 @@ def delete_question(
if not db_question: if not db_question:
raise HTTPException(status_code=404, detail="Pregunta no encontrada") 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 # Registrar auditoría antes de eliminar
audit_log = models.QuestionAuditLog( audit_log = models.QuestionAuditLog(
question_id=question_id, question_id=question_id,
@@ -1061,9 +1080,16 @@ def delete_question(
) )
db.add(audit_log) 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() 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]) @app.get("/api/questions/{question_id}/audit", response_model=List[schemas.QuestionAuditLog])

View File

@@ -83,7 +83,11 @@ class Question(Base):
# AI Analysis # AI Analysis
ai_prompt = Column(Text, nullable=True) # Prompt personalizado para análisis de IA de esta pregunta 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()) created_at = Column(DateTime(timezone=True), server_default=func.now())
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
# Relationships # Relationships
checklist = relationship("Checklist", back_populates="questions") checklist = relationship("Checklist", back_populates="questions")

View File

@@ -126,6 +126,7 @@ class QuestionBase(BaseModel):
parent_question_id: Optional[int] = None parent_question_id: Optional[int] = None
show_if_answer: Optional[str] = None show_if_answer: Optional[str] = None
ai_prompt: Optional[str] = None ai_prompt: Optional[str] = None
is_deleted: bool = False
class QuestionCreate(QuestionBase): class QuestionCreate(QuestionBase):
checklist_id: int checklist_id: int
@@ -137,6 +138,7 @@ class Question(QuestionBase):
id: int id: int
checklist_id: int checklist_id: int
created_at: datetime created_at: datetime
updated_at: Optional[datetime] = None
class Config: class Config:
from_attributes = True from_attributes = True

View File

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

View File

@@ -1050,8 +1050,6 @@ function QuestionsManagerModal({ checklist, onClose }) {
} }
const handleEditQuestion = (question) => { const handleEditQuestion = (question) => {
console.log('Editando pregunta:', question)
console.log('AI Prompt de la pregunta:', question.ai_prompt)
setEditingQuestion(question) setEditingQuestion(question)
setShowCreateForm(false) setShowCreateForm(false)
setFormData({ setFormData({
@@ -1152,12 +1150,20 @@ function QuestionsManagerModal({ checklist, onClose }) {
if (response.ok) { if (response.ok) {
loadQuestions() loadQuestions()
alert('✅ Pregunta eliminada exitosamente')
} else { } 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) { } catch (error) {
console.error('Error:', error) console.error('Error:', error)
alert('Error al eliminar pregunta') alert('Error de conexión al eliminar pregunta')
} }
} }

View File

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