From 202b053698a930ddeb0d5dd8192fd6355f0cd61d Mon Sep 17 00:00:00 2001 From: Luis Date: Fri, 5 Dec 2025 08:16:16 -0700 Subject: [PATCH 1/3] fix/reubicacion del documentos del detalle de pedimentos --- api/customs/serializers.py | 132 +++++++++++++++++++++++++++++++++++++ api/record/urls.py | 12 +++- api/record/views.py | 76 ++++++++++++++++++++- 3 files changed, 217 insertions(+), 3 deletions(-) diff --git a/api/customs/serializers.py b/api/customs/serializers.py index 89e8bf0..ec675b5 100644 --- a/api/customs/serializers.py +++ b/api/customs/serializers.py @@ -9,6 +9,7 @@ from api.customs.models import ( Partida ) from django.db import models +from django.db.models import Q from api.record.models import Document # Asegúrate de importar el modelo Documento from api.record.serializers import DocumentSerializer from api.vucem.serializers import VucemSerializer @@ -43,6 +44,59 @@ class PedimentoSerializer(serializers.ModelSerializer): return rep class PartidaSerializer(serializers.ModelSerializer): + documentos = serializers.SerializerMethodField() + + def get_documentos(self, obj): + """ + Busca documentos en la tabla `document` que coincidan EXACTAMENTE con: + 'documents/vu_PT_{pedimentoApp}_{numero}' al inicio del nombre del archivo. + """ + + if not obj or not getattr(obj, 'pedimento', None): + return [] + + if not obj or not getattr(obj, 'numero_partida', None): + return [] + + try: + pedimentoApp = str(obj.pedimento.pedimento_app).strip() + numero = str(obj.numero_partida).strip() + + # Construir el patrón exacto de búsqueda + patron_exacto = f'documents/vu_PT_{pedimentoApp}_{numero}.xml' + + # Buscar documentos que empiecen EXACTAMENTE con ese patrón + qs = Document.objects.filter( + archivo=patron_exacto + ) + + # Opción 2: Si puede tener diferentes extensiones + # patron_base = f'documents/vu_PT_{pedimentoApp}_{numero}' + # qs = Document.objects.filter( + # archivo__startswith=patron_base + # ).filter( + # archivo__in=[ + # f'{patron_base}.xml', + # f'{patron_base}.pdf', + # f'{patron_base}.zip' + # ] + # ) + + # Filtro adicional por pedimento si el modelo Document tiene este campo + if hasattr(Document, 'pedimento'): + qs = qs.filter(pedimento=obj.pedimento) + + # Filtro por organización + if hasattr(obj, 'organizacion') and obj.organizacion: + qs = qs.filter(organizacion=obj.organizacion) + + serializer = DocumentSerializer(qs, many=True, context=self.context) + return serializer.data + + #return [] + except Exception: + # En caso de cualquier error (por ejemplo, importaciones circulares), devolver lista vacía + return [] class Meta: model = Partida fields = '__all__' @@ -129,6 +183,47 @@ class ProcesamientoPedimentoSerializer(serializers.ModelSerializer): return representation class EDocumentSerializer(serializers.ModelSerializer): + documentos = serializers.SerializerMethodField() + + def get_documentos(self, obj): + """ + Busca documentos en la tabla `document` que coincidan con el + `numero_edocument` dentro del nombre del archivo (`archivo`). Se + filtra por organización para evitar devolver documentos de otras orgs. + Devuelve la serialización completa de los documentos encontrados: + 1. Empiecen con 'vu_EDOCUMENT' en el nombre del archivo + 2. Terminen con el numero_edocument + .xml + 3. Pertenezcan a la misma organización + """ + if not obj or not getattr(obj, 'numero_edocument', None): + return [] + + if not obj or not getattr(obj, 'pedimento', None): + return [] + + # if not obj or not getattr(obj, 'pedimento_id', None): + # return [] + + try: + numero = str(obj.numero_edocument).strip() + # id_pedimento = str(obj.pedimento_id).strip() + + qs = Document.objects.filter( + pedimento=obj.pedimento, + archivo__icontains=numero, + ) + + # Filtro por organización si aplica + if hasattr(obj, 'organizacion') and obj.organizacion: + qs = qs.filter(organizacion=obj.organizacion) + + serializer = DocumentSerializer(qs, many=True, context=self.context) + return serializer.data + + except Exception: + # En caso de cualquier error (por ejemplo, importaciones circulares), devolver lista vacía + return [] + class Meta: model = EDocument fields = '__all__' @@ -142,11 +237,48 @@ class EDocumentSerializer(serializers.ModelSerializer): self.fields['organizacion'].read_only = True class CoveSerializer(serializers.ModelSerializer): + documentos = serializers.SerializerMethodField() + class Meta: model = Cove fields = '__all__' read_only_fields = ('created_at', 'updated_at') + def get_documentos(self, obj): + """ + Busca documentos en la tabla `document` que coincidan con el + `numero_cove` dentro del nombre del archivo (`archivo`). Se + filtra por organización para evitar devolver documentos de otras orgs. + Devuelve la serialización completa de los documentos encontrados: + 1. Empiecen con 'vu_COVE' en el nombre del archivo + 2. Terminen con el numero_cove + .xml + 3. Pertenezcan a la misma organización + """ + if not obj or not getattr(obj, 'numero_cove', None): + return [] + + if not obj or not getattr(obj, 'pedimento', None): + return [] + + try: + numero = str(obj.numero_cove).strip() + + qs = Document.objects.filter( + pedimento=obj.pedimento, + archivo__icontains=numero, + ) + + # Filtro por organización si aplica + if hasattr(obj, 'organizacion') and obj.organizacion: + qs = qs.filter(organizacion=obj.organizacion) + + serializer = DocumentSerializer(qs, many=True, context=self.context) + return serializer.data + + except Exception: + # En caso de cualquier error (por ejemplo, importaciones circulares), devolver lista vacía + return [] + class ImportadorSerializer(serializers.ModelSerializer): class Meta: model = Importador diff --git a/api/record/urls.py b/api/record/urls.py index e8781a8..1ea522d 100644 --- a/api/record/urls.py +++ b/api/record/urls.py @@ -4,7 +4,16 @@ from rest_framework.routers import DefaultRouter # import necessary viewsets # from .views import YourViewSet # Import your viewsets here -from .views import DocumentViewSet, ProtectedDocumentDownloadView, BulkDownloadZipView, GetFuenteView, DocumentTypeView, ExpedienteZipDownloadView, MultiPedimentoZipDownloadView +from .views import (DocumentViewSet + , ProtectedDocumentDownloadView + , BulkDownloadZipView + , GetFuenteView + , DocumentTypeView + , ExpedienteZipDownloadView + , MultiPedimentoZipDownloadView + , PedimentoDocumentViewSet) + + # Create a router and register your viewsets with it router = DefaultRouter() @@ -25,5 +34,6 @@ urlpatterns = [ path('document-type/', DocumentTypeView.as_view(), name='document-type-list-create'), path('documents/expediente-zip/', ExpedienteZipDownloadView.as_view(), name='expediente-zip-download'), path('documents/multi-pedimento-zip/', MultiPedimentoZipDownloadView.as_view(), name='multi-pedimento-zip-download'), + path('pedimento-documents/', PedimentoDocumentViewSet.as_view({'get': 'list'}), name='pedimento-document-list'), path('', include(router.urls)), ] \ No newline at end of file diff --git a/api/record/views.py b/api/record/views.py index 7c33d45..f2688f9 100644 --- a/api/record/views.py +++ b/api/record/views.py @@ -63,7 +63,8 @@ class DocumentViewSet(viewsets.ModelViewSet, DocumentosFiltradosMixin): pagination_class = CustomPagination serializer_class = DocumentSerializer # Habilitar filtro por pedimento (UUID) y pedimento_numero (campo pedimento del modelo relacionado) - filterset_fields = ['extension', 'size', 'document_type', 'pedimento', 'pedimento__pedimento'] + filterset_fields = ['extension', 'size', 'document_type', 'pedimento', 'pedimento__pedimento', 'created_at'] + # filterset_fields = ['extension', 'size', 'pedimento', 'pedimento__pedimento'] # Puedes filtrar por pedimento usando: /api/record/documents/?pedimento= o /api/record/documents/?pedimento__pedimento= # Ejemplo: /api/record/documents/?pedimento_numero=12345678 @@ -71,6 +72,33 @@ class DocumentViewSet(viewsets.ModelViewSet, DocumentosFiltradosMixin): def get_queryset(self): queryset = self.get_queryset_filtrado_por_organizacion() + + modulo_efc = self.request.query_params.get('modulo') + if modulo_efc: + if modulo_efc == 'expedientes-detalle-pedimentos': + queryset = queryset.filter(document_type_id='11') + # Filtro personalizado por document_type + # document_type = self.request.query_params.get('document_type') + # if document_type: + # # Puedes agregar lógica personalizada aquí si es necesario + # if document_type == '1': + # queryset = queryset.filter(document_type_id=document_type) + # elif document_type == '2': + # queryset = queryset.filter(document_type_id=document_type) + # else: + # queryset = queryset.filter(document_type_id=document_type) + # else: + # queryset = queryset.filter(document_type_id='11') + + fechaCreacion = self.request.query_params.get('created_at__date') + if fechaCreacion: + queryset = queryset.filter(created_at=fechaCreacion) + + buscarArchivo = self.request.query_params.get('archivo__icontains') + if buscarArchivo: + queryset = queryset.filter(archivo__icontains=buscarArchivo) + + pedimento_numero = self.request.query_params.get('pedimento_numero') if pedimento_numero: queryset = queryset.filter(pedimento__pedimento_app=pedimento_numero) @@ -762,6 +790,50 @@ class MultiPedimentoZipDownloadView(APIView): return response - +class PedimentoDocumentViewSet(viewsets.ModelViewSet, DocumentosFiltradosMixin): + """ + ViewSet for Document model. + """ + permission_classes = [IsAuthenticated & (IsSuperUser | IsSameOrganization | IsSameOrganizationAndAdmin | IsSameOrganizationDeveloper )] + model = Document + + pagination_class = CustomPagination + serializer_class = DocumentSerializer + # Habilitar filtro por pedimento (UUID) y pedimento_numero (campo pedimento del modelo relacionado) + # filterset_fields = ['extension', 'size', 'document_type', 'pedimento', 'pedimento__pedimento'] + filterset_fields = ['extension', 'size', 'pedimento', 'pedimento__pedimento','fuente'] + + # Puedes filtrar por pedimento usando: /api/record/documents/?pedimento= o /api/record/documents/?pedimento__pedimento= + # Ejemplo: /api/record/documents/?pedimento_numero=12345678 + my_tags = ['Documents'] + + def get_queryset(self): + queryset = self.get_queryset_filtrado_por_organizacion() + + # Tipos de documento permitidos (fijos en código, Pedimento completo y remesas) + TIPOS_PERMITIDOS = ['2', '3'] # <-- Ajusta aquí tus tipos + tipo_documento = self.request.query_params.get('document_type') + if tipo_documento: + queryset = queryset.filter(document_type_id=tipo_documento) + else: + # Filtrar por tipos permitidos + queryset = queryset.filter(document_type_id__in=TIPOS_PERMITIDOS) + + buscar_archivo = self.request.query_params.get('archivo__icontains') + if buscar_archivo: + queryset = queryset.filter(archivo__icontains=buscar_archivo) + + created_at__date = self.request.query_params.get('created_at__date') + if created_at__date: + queryset = queryset.filter(created_at=created_at__date) + + # Filtro adicional por pedimento_numero si se proporciona + pedimento_numero = self.request.query_params.get('pedimento_numero') + if pedimento_numero: + queryset = queryset.filter(pedimento__pedimento_app=pedimento_numero) + + return queryset + + \ No newline at end of file From ed63a4854c8f316e22b9b70652b2ada589654812 Mon Sep 17 00:00:00 2001 From: Luis Date: Wed, 10 Dec 2025 11:30:58 -0700 Subject: [PATCH 2/3] fix: Se ajusta carga de expediente en RAR y se agega validacion para evitar duplicar el pedimento independientemente de la organizacion de carga. --- .gitignore | 2 +- Dockerfile | 9 +- api/customs/views.py | 191 +++++++++++++++++++++++++++++++++++-------- api/record/views.py | 2 +- 4 files changed, 167 insertions(+), 37 deletions(-) diff --git a/.gitignore b/.gitignore index a386bea..e9e644d 100644 --- a/.gitignore +++ b/.gitignore @@ -178,4 +178,4 @@ cython_debug/ #.idea/ # End of https://www.toptal.com/developers/gitignore/api/django - +*.bak diff --git a/Dockerfile b/Dockerfile index 22017e5..032e250 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,12 +3,17 @@ FROM python:3.11-slim WORKDIR /app +# Instalar dependencias del sistema necesarias +RUN apt-get update && apt-get install -y --no-install-recommends wget && \ + wget https://www.rarlab.com/rar/rarlinux-x64-621.tar.gz && \ + tar -xzvf rarlinux*.tar.gz && \ + cp rar/unrar /usr/bin/unrar && \ + rm -rf rarlinux*.tar.gz rar + COPY requirements.txt ./ RUN pip install --no-cache-dir -r requirements.txt - RUN pip install flower - COPY . . diff --git a/api/customs/views.py b/api/customs/views.py index 168d1e3..4bddd38 100644 --- a/api/customs/views.py +++ b/api/customs/views.py @@ -57,6 +57,22 @@ try: except ImportError: RAR_SUPPORT = False +def get_available_extractors(): + """ + Devuelve lista de extractores disponibles en orden de preferencia + """ + extractors = [] + if RAR_SUPPORT: + extractors.append('rarfile') + # Verificar si 'unrar' está disponible + if shutil.which('unrar'): + extractors.append('unrar') + # Verificar si '7z' o '7za' están disponibles + if shutil.which('7z'): + extractors.append('7z') + elif shutil.which('7za'): + extractors.append('7za') + return extractors def extract_rar_to_dir(rar_path, dest_dir): """ @@ -67,12 +83,29 @@ def extract_rar_to_dir(rar_path, dest_dir): Lanza Exception con mensaje explicativo si falla. """ - # Intento con rarfile (Python) - if RAR_SUPPORT: + + # Versión que primero verifica herramientas disponibles + available = get_available_extractors() + if not available: + raise Exception("No hay herramientas de extracción disponibles.") + + print(f"Extractores disponibles (en orden de preferencia): {available}") + + # Intento con rarfile primero si está disponible + # if RAR_SUPPORT: + if 'rarfile' in available and RAR_SUPPORT: try: # rarfile puede trabajar con rutas en disco mejor que con file-like with rarfile.RarFile(rar_path) as rf: rf.extractall(dest_dir) + + try: + if os.path.exists(rar_path): + os.remove(rar_path) + print(f"Archivo original eliminado: {rar_path}") + except OSError as remove_error: + print(f"Advertencia: No se pudo eliminar '{rar_path}': {remove_error}") + return except Exception as e: # Si rarfile falla (por ejemplo RarCannotExec), seguimos con herramientas externas @@ -87,18 +120,49 @@ def extract_rar_to_dir(rar_path, dest_dir): ['7za', 'x', rar_path, f'-o{dest_dir}', '-y'] ] - for cmd in external_cmds: - try: - subprocess.run(cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - return - except FileNotFoundError: - # El ejecutable no existe en PATH, intentar siguiente - continue - except subprocess.CalledProcessError as e: - # El comando falló en la extracción; intentar siguiente - print(f"External extractor failed ({cmd[0]}): {e}") + # for cmd in external_cmds: + # try: + # subprocess.run(cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + + # try: + # if os.path.exists(rar_path): + # os.remove(rar_path) + # print(f"Archivo original eliminado: {rar_path}") + # except OSError as remove_error: + # print(f"Advertencia: No se pudo eliminar '{rar_path}': {remove_error}") + + # return + # except FileNotFoundError: + # # El ejecutable no existe en PATH, intentar siguiente + # continue + # except subprocess.CalledProcessError as e: + # # El comando falló en la extracción; intentar siguiente + # print(f"External extractor failed ({cmd[0]}): {e}") + # continue + + for extractor_name in available: + if extractor_name in external_cmds: + cmd = external_cmds[extractor_name] + try: + subprocess.run(cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + + try: + if os.path.exists(rar_path): + os.remove(rar_path) + print(f"Archivo original eliminado: {rar_path}") + except OSError as remove_error: + print(f"Advertencia: No se pudo eliminar '{rar_path}': {remove_error}") + + return + except FileNotFoundError: + # El ejecutable no existe en PATH, intentar siguiente + continue + except subprocess.CalledProcessError as e: + # El comando falló en la extracción; intentar siguiente + print(f"External extractor {extractor_name} failed ({cmd[0]}): {e}") continue + # Si llegamos aquí, ningún método funcionó raise Exception("No se encontró una herramienta válida para extraer RAR (rarfile sin backend, 'unrar' o '7z' no disponibles o extracción fallida). Instale 'unrar' o 'p7zip' y asegúrese de que estén en PATH, o configure rarfile con un backend.") from .tasks.microservice_v2 import * @@ -212,7 +276,10 @@ class ViewSetPedimento(LoggingMixin, viewsets.ModelViewSet, OrganizacionFiltrada filterset_fields = ['patente', 'aduana', 'tipo_operacion', 'clave_pedimento', 'pedimento', 'existe_expediente', 'contribuyente', 'curp_apoderado', 'fecha_pago', 'pedimento_app'] search_fields = ['pedimento', 'pedimento_app', 'agente_aduanal', 'clave_pedimento'] - + # AGREGAR ESTOS CAMPOS PARA ORDENACIÓN + ordering_fields = ['created_at', 'pedimento', 'fecha_pago', 'aduana', 'patente'] + ordering = ['-created_at'] # Orden descendente por fecha de creación por defecto + def get_queryset(self): return self.get_queryset_filtrado_por_organizacion() # Tambien filtra por importador @@ -444,6 +511,7 @@ class ViewSetPedimento(LoggingMixin, viewsets.ModelViewSet, OrganizacionFiltrada # Regex para validar nomenclatura: anio-aduana-patente-pedimento nomenclatura_pattern = re.compile(r'^(\d{2})-(\d{2,3})-(\d{4})-(\d{7})$') + nomenclatura_pattern_sin_anio = re.compile(r'^(\d{2,3})-(\d{4})-(\d{7})$') created_pedimentos = [] failed_files = [] @@ -591,7 +659,9 @@ class ViewSetPedimento(LoggingMixin, viewsets.ModelViewSet, OrganizacionFiltrada # Validar nomenclatura match = nomenclatura_pattern.match(folder_name) - if not match: + match_sin_anio = nomenclatura_pattern_sin_anio.match(folder_name) + + if not match and not match_sin_anio: print(f"Nomenclatura inválida: {folder_name}") # Determinar el archivo original basado en el subdirectorio archivo_original = folder_name + ('.zip' if any(f.endswith('.zip') for f in [a.name for a in archivos]) else '.rar') @@ -602,25 +672,60 @@ class ViewSetPedimento(LoggingMixin, viewsets.ModelViewSet, OrganizacionFiltrada }) continue - print(f"Nomenclatura válida: {folder_name}") - anio, aduana, patente, pedimento_num = match.groups() - print(f"Extraído - Año: {anio}, Aduana: {aduana}, Patente: {patente}, Pedimento: {pedimento_num}") - - # Crear fecha_pago basada en el año - try: - # Convertir año de 2 dígitos a 4 dígitos - anio_completo = 2000 + int(anio) if int(anio) < 50 else 1900 + int(anio) - fecha_pago = datetime(anio_completo, 1, 1).date() - print(f"Fecha de pago calculada: {fecha_pago}") - except ValueError: - archivo_original = folder_name + ('.zip' if any(f.endswith('.zip') for f in [a.name for a in archivos]) else '.rar') - failed_files.append({ - "file": relative_path, - "archivo_original": archivo_original, - "error": f"Año inválido: {anio}" - }) - continue + if match: + + print(f"Nomenclatura válida: {folder_name}") + anio, aduana, patente, pedimento_num = match.groups() + print(f"Extraído - Año: {anio}, Aduana: {aduana}, Patente: {patente}, Pedimento: {pedimento_num}") + # Formato original: anio-aduana-patente-pedimento + # Crear fecha_pago basada en el año + try: + # Convertir año de 2 dígitos a 4 dígitos + anio_completo = 2000 + int(anio) if int(anio) < 50 else 1900 + int(anio) + fecha_pago = datetime(anio_completo, 1, 1).date() + print(f"Fecha de pago calculada: {fecha_pago}") + except ValueError: + archivo_original = folder_name + ('.zip' if any(f.endswith('.zip') for f in [a.name for a in archivos]) else '.rar') + failed_files.append({ + "file": relative_path, + "archivo_original": archivo_original, + "error": f"Año inválido: {anio}" + }) + continue + + elif match_sin_anio: + + print(f"Nomenclatura válida sin año: {folder_name}") + # Formato sin año: aduana-patente-pedimento + aduana, patente, pedimento_num = match_sin_anio.groups() + print(f"Extraído - Aduana: {aduana}, Patente: {patente}, Pedimento: {pedimento_num}") + + # Obtener el primer dígito del pedimento + primer_digito_pedimento = int(pedimento_num[0]) if pedimento_num else 0 + + # Usar año actual para fecha_pago y ajustar según el dígito del pedimento + año_actual = datetime.now().year + + # Crear año con el dígito del pedimento (reemplazando el último dígito) + año_con_digito = int(str(año_actual)[:-1] + str(primer_digito_pedimento)) + + # Aplicar lógica de comparación + if año_con_digito <= año_actual: + # Si el año con dígito es menor o igual al año actual + año_final = año_con_digito + else: + # Si el año con dígito es mayor al año actual, restar 10 + año_final = año_con_digito - 10 + + # Tomar los últimos 2 dígitos del año final + anio = año_final % 100 + + # Crear fecha de pago (primer día del año) + fecha_pago = datetime(año_final , 1, 1).date() + + print(f"Fecha de pago (año actual) calculada: {fecha_pago}") + # Generar pedimento_app pedimento_app = f"{anio}-{aduana.zfill(2)}-{patente}-{pedimento_num}" print(f"Pedimento_app generado: {pedimento_app}") @@ -628,7 +733,7 @@ class ViewSetPedimento(LoggingMixin, viewsets.ModelViewSet, OrganizacionFiltrada # Verificar si el pedimento ya existe existing_pedimento = Pedimento.objects.filter( pedimento_app=pedimento_app, - organizacion=organizacion + # organizacion=organizacion ).first() print(f"Pedimento existente: {existing_pedimento is not None}") @@ -700,12 +805,32 @@ class ViewSetPedimento(LoggingMixin, viewsets.ModelViewSet, OrganizacionFiltrada # Crear ContentFile que Django puede manejar correctamente django_file = ContentFile(file_content, name=file_name) + # # Verificar si el documento ya existe para este pedimento y archivo + # print("🔍 Verificando existencia previa del documento...") + + # # Reemplazar múltiples caracteres + # normalized_file_name = file_name.replace(" ", "_") + + # file_name_without_extension = normalized_file_name.rsplit('.', 1)[0] + # extension_file = os.path.splitext(normalized_file_name)[1].lower().lstrip('.') + + # existing_document = Document.objects.filter( + # pedimento_id=pedimento.id, + # archivo__contains=file_name_without_extension, + # extension=extension_file + # ).first() + + # if existing_document: + # print(f"Documento existente encontrado, omitiendo creación: ID {existing_document.id}") + # continue + print(f"Creando documento para archivo: {file_name}") # Crear documento - Django automáticamente guardará el archivo en media/documents/ document = Document.objects.create( organizacion=organizacion, pedimento_id=pedimento.id, document_type=document_type, + fuente_id=4, # Fuente: Carga Plataforma archivo=django_file, size=len(file_content), extension=os.path.splitext(file_name)[1].lower().lstrip('.') diff --git a/api/record/views.py b/api/record/views.py index f2688f9..888aee2 100644 --- a/api/record/views.py +++ b/api/record/views.py @@ -76,7 +76,7 @@ class DocumentViewSet(viewsets.ModelViewSet, DocumentosFiltradosMixin): modulo_efc = self.request.query_params.get('modulo') if modulo_efc: if modulo_efc == 'expedientes-detalle-pedimentos': - queryset = queryset.filter(document_type_id='11') + queryset = queryset.exclude(document_type_id__in=['1','2','3','4','5','6','7','8','9','10']) # Filtro personalizado por document_type # document_type = self.request.query_params.get('document_type') # if document_type: From 97ac547a4b27e9091c75bec1ff7e81c28d031054 Mon Sep 17 00:00:00 2001 From: Luis Date: Wed, 10 Dec 2025 11:44:13 -0700 Subject: [PATCH 3/3] fix: Se modifica Dockerfile.prod para que instale unrar desde pagina oficial. --- Dockerfile.prod | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Dockerfile.prod b/Dockerfile.prod index 21f0b35..7fdeb8d 100644 --- a/Dockerfile.prod +++ b/Dockerfile.prod @@ -3,8 +3,16 @@ FROM python:3.11-slim WORKDIR /app # Instalar dependencias del sistema -RUN apt-get update && apt-get install -y \ +# RUN apt-get update && apt-get install -y \ +# supervisor \ +# && rm -rf /var/lib/apt/lists/* +RUN apt-get update && apt-get install -y --no-install-recommends \ supervisor \ + wget \ + && wget https://www.rarlab.com/rar/rarlinux-x64-621.tar.gz \ + && tar -xzvf rarlinux*.tar.gz \ + && cp rar/unrar /usr/bin/unrar \ + && rm -rf rarlinux*.tar.gz rar \ && rm -rf /var/lib/apt/lists/* # Copiar e instalar dependencias de Python