diff --git a/backend/app/main.py b/backend/app/main.py index 28bdd33..363559c 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -207,7 +207,7 @@ def send_completed_inspection_to_n8n(inspection, db): # No lanzamos excepción para no interrumpir el flujo normal -BACKEND_VERSION = "1.0.89" +BACKEND_VERSION = "1.0.90" app = FastAPI(title="Checklist Inteligente API", version=BACKEND_VERSION) # S3/MinIO configuration @@ -2558,11 +2558,52 @@ async def analyze_image( "message": "No hay configuración de IA activa. Configure en Settings." } - # Guardar imagen temporalmente + # Guardar archivo temporalmente y procesar según tipo import base64 + from pypdf import PdfReader contents = await file.read() - image_b64 = base64.b64encode(contents).decode('utf-8') + file_type = file.content_type + + print(f"📄 Tipo de archivo: {file_type}") + + # Detectar si es PDF + is_pdf = file_type == 'application/pdf' or file.filename.lower().endswith('.pdf') + + if is_pdf: + print("📕 Detectado PDF - extrayendo texto...") + try: + from io import BytesIO + pdf_file = BytesIO(contents) + pdf_reader = PdfReader(pdf_file) + + # Extraer texto de todas las páginas + pdf_text = "" + for page_num, page in enumerate(pdf_reader.pages): + pdf_text += f"\n--- Página {page_num + 1} ---\n" + pdf_text += page.extract_text() + + print(f"✅ Texto extraído: {len(pdf_text)} caracteres") + + if not pdf_text.strip(): + return { + "status": "error", + "message": "No se pudo extraer texto del PDF. Puede ser un PDF escaneado sin OCR." + } + + # Para PDFs usamos análisis de texto, no de imagen + image_b64 = None + + except Exception as e: + print(f"❌ Error al procesar PDF: {str(e)}") + return { + "status": "error", + "message": f"Error al procesar PDF: {str(e)}" + } + else: + # Es una imagen + image_b64 = base64.b64encode(contents).decode('utf-8') + pdf_text = None # Obtener contexto de la pregunta si se proporciona question_obj = None @@ -2714,6 +2755,13 @@ NOTA: - "status" debe ser "ok" (bueno), "minor" (problemas leves) o "critical" (problemas graves) - "context_match" debe ser true si la imagen muestra un componente vehicular relevante, false si no corresponde.""" user_message = "Analiza este componente del vehículo para la inspección general." + + # Ajustar prompt si es PDF en lugar de imagen + if is_pdf: + system_prompt = system_prompt.replace("Analiza la imagen", "Analiza el documento PDF") + system_prompt = system_prompt.replace("la imagen", "el documento") + system_prompt = system_prompt.replace("context_match", "document_relevance") + user_message = user_message.replace("imagen", "documento PDF") print(f"\n🤖 PROMPT ENVIADO AL AI:") print(f"Provider: {ai_config.provider}") @@ -2726,9 +2774,19 @@ NOTA: import openai openai.api_key = ai_config.api_key - response = openai.ChatCompletion.create( - model=ai_config.model_name, - messages=[ + # Construir mensaje según si es PDF o imagen + if is_pdf: + # Para PDF, solo texto + messages_content = [ + {"role": "system", "content": system_prompt}, + { + "role": "user", + "content": f"{user_message}\n\n--- CONTENIDO DEL DOCUMENTO PDF ---\n{pdf_text[:4000]}" # Limitar a 4000 chars + } + ] + else: + # Para imagen, usar vision + messages_content = [ {"role": "system", "content": system_prompt}, { "role": "user", @@ -2743,7 +2801,11 @@ NOTA: } ] } - ], + ] + + response = openai.ChatCompletion.create( + model=ai_config.model_name, + messages=messages_content, max_tokens=500 ) @@ -2757,11 +2819,17 @@ NOTA: genai.configure(api_key=ai_config.api_key) model = genai.GenerativeModel(ai_config.model_name) - # Convertir base64 a imagen PIL - image = Image.open(BytesIO(contents)) - prompt = f"{system_prompt}\n\n{user_message}" - response = model.generate_content([prompt, image]) + + if is_pdf: + # Para PDF, solo texto + prompt_with_content = f"{prompt}\n\n--- CONTENIDO DEL DOCUMENTO PDF ---\n{pdf_text[:4000]}" + response = model.generate_content(prompt_with_content) + else: + # Para imagen, incluir imagen + image = Image.open(BytesIO(contents)) + response = model.generate_content([prompt, image]) + ai_response = response.text else: diff --git a/backend/requirements.txt b/backend/requirements.txt index 7f2fefd..94118a6 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -14,6 +14,7 @@ openai==1.10.0 google-generativeai==0.3.2 Pillow==10.2.0 reportlab==4.0.9 +pypdf==4.0.1 python-dotenv==1.0.0 boto3==1.34.89 requests==2.31.0 \ No newline at end of file diff --git a/frontend/package.json b/frontend/package.json index b0bbede..18a88fd 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,7 +1,7 @@ { "name": "checklist-frontend", "private": true, - "version": "1.0.91", + "version": "1.0.92", "type": "module", "scripts": { "dev": "vite", diff --git a/frontend/public/service-worker.js b/frontend/public/service-worker.js index 62256f4..5d8182c 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.91'; +const CACHE_NAME = 'ayutec-v1.0.92'; const urlsToCache = [ '/', '/index.html' diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index c24bdd3..0ebdcb9 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -5007,10 +5007,10 @@ function InspectionModal({ checklist, existingInspection, user, onClose, onCompl {currentQuestion.allow_photos && (