From c4f5d960de0ae68a2966b54db959655d1959fa0f Mon Sep 17 00:00:00 2001 From: ronalds Date: Tue, 2 Dec 2025 22:22:51 -0300 Subject: [PATCH] =?UTF-8?q?=20=E2=9C=85=20Nueva=20Funcionalidad:=203=20Est?= =?UTF-8?q?ados=20para=20Adjuntos=20(Ninguno/Opcional/Obligatorio)=20He=20?= =?UTF-8?q?implementado=20el=20sistema=20de=203=20estados=20para=20el=20re?= =?UTF-8?q?quisito=20de=20fotos/archivos=20que=20solicitaste.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Problema Original: Solo había 2 estados: ✅ Permitir fotos (checkbox activado) ❌ No permitir fotos (checkbox desactivado) Faltaba: Fotos opcionales vs obligatorias Solución Implementada: 3 Estados disponibles: 🚫 No permitir adjuntos (photo_requirement = 'none') No se muestra el input de fotos El mecánico NO puede adjuntar archivos 📎 Opcional (photo_requirement = 'optional') Se muestra el input de fotos El mecánico PUEDE adjuntar si lo desea No es obligatorio para continuar ⚠️ Obligatorio (photo_requirement = 'required') Se muestra el input de fotos con etiqueta "OBLIGATORIO" El mecánico DEBE adjuntar al menos 1 archivo Validación bloquea continuar sin adjuntos --- backend/app/main.py | 2 +- backend/app/models.py | 3 +- frontend/package.json | 2 +- frontend/public/service-worker.js | 2 +- frontend/src/App.jsx | 113 +++++++++++++----- ...ange_allow_photos_to_photo_requirement.sql | 22 ++++ 6 files changed, 109 insertions(+), 35 deletions(-) create mode 100644 migrations/change_allow_photos_to_photo_requirement.sql diff --git a/backend/app/main.py b/backend/app/main.py index d132cb7..fe90e8d 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -209,7 +209,7 @@ def send_completed_inspection_to_n8n(inspection, db): # No lanzamos excepción para no interrumpir el flujo normal -BACKEND_VERSION = "1.0.93" +BACKEND_VERSION = "1.0.94" app = FastAPI(title="Checklist Inteligente API", version=BACKEND_VERSION) # S3/MinIO configuration diff --git a/backend/app/models.py b/backend/app/models.py index 7d07411..6138ae0 100644 --- a/backend/app/models.py +++ b/backend/app/models.py @@ -70,7 +70,8 @@ class Question(Base): points = Column(Integer, default=1) options = Column(JSON) # Configuración flexible según tipo de pregunta order = Column(Integer, default=0) - allow_photos = Column(Boolean, default=True) + allow_photos = Column(Boolean, default=True) # DEPRECATED: usar photo_requirement + photo_requirement = Column(String(20), default='optional') # none, optional, required max_photos = Column(Integer, default=3) requires_comment_on_fail = Column(Boolean, default=False) send_notification = Column(Boolean, default=False) diff --git a/frontend/package.json b/frontend/package.json index 72f3d3a..e1f5e91 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,7 +1,7 @@ { "name": "checklist-frontend", "private": true, - "version": "1.0.99", + "version": "1.1.0", "type": "module", "scripts": { "dev": "vite", diff --git a/frontend/public/service-worker.js b/frontend/public/service-worker.js index a25bd54..e1fcd1e 100644 --- a/frontend/public/service-worker.js +++ b/frontend/public/service-worker.js @@ -1,6 +1,6 @@ // Service Worker para PWA con detección de actualizaciones // IMPORTANTE: Actualizar esta versión cada vez que se despliegue una nueva versión -const CACHE_NAME = 'ayutec-v1.0.99'; +const CACHE_NAME = 'ayutec-v1.1.0'; const urlsToCache = [ '/', '/index.html' diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 0e87f2d..f48bbd4 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -1058,6 +1058,7 @@ function QuestionsManagerModal({ checklist, onClose }) { ] }, allow_photos: true, + photo_requirement: 'optional', max_photos: 3, requires_comment_on_fail: false, send_notification: false, @@ -1149,6 +1150,7 @@ function QuestionsManagerModal({ checklist, onClose }) { ] }, allow_photos: true, + photo_requirement: 'optional', max_photos: 3, requires_comment_on_fail: false, send_notification: false, @@ -1182,6 +1184,7 @@ function QuestionsManagerModal({ checklist, onClose }) { ] }, allow_photos: question.allow_photos ?? true, + photo_requirement: question.photo_requirement || 'optional', max_photos: question.max_photos || 3, requires_comment_on_fail: question.requires_comment_on_fail || false, send_notification: question.send_notification || false, @@ -1212,6 +1215,7 @@ function QuestionsManagerModal({ checklist, onClose }) { points: parseInt(formData.points), options: formData.options, allow_photos: formData.allow_photos, + photo_requirement: formData.photo_requirement, max_photos: parseInt(formData.max_photos), requires_comment_on_fail: formData.requires_comment_on_fail, send_notification: formData.send_notification, @@ -1236,6 +1240,7 @@ function QuestionsManagerModal({ checklist, onClose }) { ] }, allow_photos: true, + photo_requirement: 'optional', max_photos: 3, requires_comment_on_fail: false, send_notification: false, @@ -1847,30 +1852,50 @@ function QuestionsManagerModal({ checklist, onClose }) { className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500" /> -
- setFormData({ ...formData, allow_photos: e.target.checked })} - className="w-4 h-4 text-purple-600 border-gray-300 rounded focus:ring-purple-500" - /> - -
-
- - setFormData({ ...formData, max_photos: parseInt(e.target.value) })} - className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500" - disabled={!formData.allow_photos} - /> +
+ + {/* Configuración de fotos/archivos */} +
+

+ 📷 Fotos y Archivos Adjuntos +

+
+
+ + +

+ {formData.photo_requirement === 'none' && '• No se podrán adjuntar fotos/archivos'} + {formData.photo_requirement === 'optional' && '• El mecánico puede adjuntar si lo desea'} + {formData.photo_requirement === 'required' && '• El mecánico DEBE adjuntar al menos 1 archivo'} +

+
+
+ + setFormData({ ...formData, max_photos: parseInt(e.target.value) })} + className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500" + disabled={formData.photo_requirement === 'none'} + /> +

+ Cantidad máxima de fotos/PDFs permitidos +

+
@@ -1992,7 +2017,15 @@ function QuestionsManagerModal({ checklist, onClose }) { {question.type} {question.points} pts - {question.allow_photos && ( + {question.photo_requirement === 'required' && ( + + ⚠️ Fotos obligatorias + + )} + {question.photo_requirement === 'optional' && ( + 📷 Máx {question.max_photos} fotos + )} + {(!question.photo_requirement || question.allow_photos) && !question.photo_requirement && ( 📷 Máx {question.max_photos} fotos )} {question.send_notification && ( @@ -4396,12 +4429,20 @@ function InspectionModal({ checklist, existingInspection, user, onClose, onCompl const validateAllAnswered = () => { const visibleQuestions = getVisibleQuestions() const unanswered = visibleQuestions.filter(q => { + const answer = answers[q.id] + // Para preguntas tipo photo_only, solo validar que tenga fotos if (q.options?.type === 'photo_only') { - return !answers[q.id]?.photos?.length + return !answer?.photos?.length } + + // Validar fotos obligatorias (photo_requirement = 'required') + if (q.photo_requirement === 'required' && (!answer?.photos || answer.photos.length === 0)) { + return true // Falta adjuntar fotos obligatorias + } + // Para otros tipos, validar que tenga respuesta - return !answers[q.id]?.value + return !answer?.value }) return unanswered } @@ -5088,13 +5129,23 @@ function InspectionModal({ checklist, existingInspection, user, onClose, onCompl )} {/* Photos */} - {currentQuestion.allow_photos && ( + {(currentQuestion.photo_requirement !== 'none' || currentQuestion.allow_photos) && (