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) && (