Soporte para PDFs agregado al sistema de análisis con IA

📋 Cambios Implementados
Frontend:

 Input acepta image/*,application/pdf
 Label actualizado: "Fotografías / Documentos PDF *"
 Preview diferenciado: PDFs muestran icono 📝 rojo en lugar de imagen
 Nombre de archivo PDF visible en el preview
 Contador genérico: "archivo(s) cargado(s)"
Backend:

 Agregado pypdf==4.0.1 a requirements.txt
 Detección automática de PDFs por content_type o extensión
 Extracción de texto de PDFs usando pypdf.PdfReader
 Validación de PDFs vacíos (sin texto extraíble)
 Prompts adaptados automáticamente para PDFs
 Soporte en OpenAI y Gemini (análisis de texto en lugar de vision)
 Límite de 4000 caracteres del PDF para análisis
🎯 Funcionamiento
Usuario sube PDF → Sistema detecta tipo de archivo
Extrae texto → PyPDF lee todas las páginas
Análisis IA → Envía texto al modelo (no usa Vision API)
Respuesta → Misma estructura JSON que con imágenes
⚠️ Limitaciones
PDFs escaneados sin OCR no funcionarán (requieren texto seleccionable)
Máximo 4000 caracteres del PDF enviados al AI
📦 Versiones
Frontend: 1.0.91 → 1.0.92
Backend: 1.0.89 → 1.0.90
This commit is contained in:
2025-12-02 09:40:44 -03:00
parent d51d912962
commit bf30b1a2bf
6 changed files with 100 additions and 24 deletions

View File

@@ -5007,10 +5007,10 @@ function InspectionModal({ checklist, existingInspection, user, onClose, onCompl
{currentQuestion.allow_photos && (
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Fotografías *
Fotografías / Documentos *
{currentQuestion.max_photos && (
<span className="ml-2 text-xs text-gray-600">
(máximo {currentQuestion.max_photos} foto{currentQuestion.max_photos > 1 ? 's' : ''})
(máximo {currentQuestion.max_photos} archivo{currentQuestion.max_photos > 1 ? 's' : ''})
</span>
)}
{(checklist.ai_mode === 'assisted' || checklist.ai_mode === 'full') && (
@@ -5023,7 +5023,7 @@ function InspectionModal({ checklist, existingInspection, user, onClose, onCompl
<input
key={`photo-input-${currentQuestion.id}`}
type="file"
accept="image/*"
accept="image/*,application/pdf"
multiple={currentQuestion.max_photos > 1}
onChange={(e) => {
handlePhotoChange(currentQuestion.id, e.target.files)
@@ -5038,16 +5038,23 @@ function InspectionModal({ checklist, existingInspection, user, onClose, onCompl
{answers[currentQuestion.id]?.photos?.length > 0 && (
<div className="mt-3 space-y-2">
<div className="text-sm font-medium text-gray-700">
{answers[currentQuestion.id].photos.length} foto(s) cargada(s):
{answers[currentQuestion.id].photos.length} archivo(s) cargado(s):
</div>
<div className="grid grid-cols-3 gap-2">
{answers[currentQuestion.id].photos.map((photo, index) => (
<div key={index} className="relative group">
<img
src={URL.createObjectURL(photo)}
alt={`Foto ${index + 1}`}
className="w-full h-24 object-cover rounded-lg border border-gray-300"
/>
{photo.type === 'application/pdf' ? (
<div className="w-full h-24 flex flex-col items-center justify-center bg-red-50 border-2 border-red-300 rounded-lg">
<span className="text-3xl">📝</span>
<span className="text-xs text-red-700 mt-1">PDF</span>
</div>
) : (
<img
src={URL.createObjectURL(photo)}
alt={`Foto ${index + 1}`}
className="w-full h-24 object-cover rounded-lg border border-gray-300"
/>
)}
<button
type="button"
onClick={() => handleRemovePhoto(currentQuestion.id, index)}
@@ -5057,7 +5064,7 @@ function InspectionModal({ checklist, existingInspection, user, onClose, onCompl
</button>
<div className="text-xs text-center text-gray-600 mt-1">
Foto {index + 1}
{photo.type === 'application/pdf' ? photo.name : `Foto ${index + 1}`}
</div>
</div>
))}