Mudanza de repo

This commit is contained in:
2025-09-22 18:43:29 -06:00
parent 26fe36ca52
commit d11d543bdc
193 changed files with 10998 additions and 0 deletions

0
api/vucem/__init__.py Normal file
View File

20
api/vucem/admin.py Normal file
View File

@@ -0,0 +1,20 @@
from django.contrib import admin
from .models import Vucem, CredencialesImportador
# Register your models here.
class VucemAdmin(admin.ModelAdmin):
list_display = ('id', 'organizacion', 'usuario', 'patente', 'is_importador', 'is_active', 'created_at', 'updated_at')
search_fields = ('usuario', 'patente')
list_filter = ('is_importador', 'acusecove', 'acuseedocument', 'is_active')
ordering = ('-created_at',)
class CredencialesImportadorAdmin(admin.ModelAdmin):
list_display = ('id', 'organizacion', 'vucem', 'rfc', 'created_at', 'updated_at')
search_fields = ('rfc',)
list_filter = ('organizacion',)
ordering = ('-created_at',)
admin.site.register(Vucem, VucemAdmin)
admin.site.register(CredencialesImportador, CredencialesImportadorAdmin)

6
api/vucem/apps.py Normal file
View File

@@ -0,0 +1,6 @@
from django.apps import AppConfig
class VucemConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'api.vucem'

View File

@@ -0,0 +1,42 @@
# Generated by Django 5.2.3 on 2025-07-14 16:14
import django.db.models.deletion
import uuid
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
('organization', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Vucem',
fields=[
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('usuario', models.CharField(help_text='Usuario de VUCEM', max_length=100, unique=True)),
('password', models.CharField(help_text='Contraseña de VUCEM', max_length=100)),
('patente', models.CharField(help_text='Patente de VUCEM', max_length=100, unique=True)),
('is_importador', models.BooleanField(default=False, help_text='Indica si es importador')),
('acusecove', models.BooleanField(default=False, help_text='Indica si generara acusecove')),
('acuseedocument', models.BooleanField(default=False, help_text='Indica si generara acusee edocumento')),
('is_active', models.BooleanField(default=True, help_text='Indica si el registro está activo')),
('created_at', models.DateTimeField(auto_now_add=True, help_text='Fecha de creación del registro')),
('updated_at', models.DateTimeField(auto_now=True, help_text='Fecha de última actualización del registro')),
('created_by', models.ForeignKey(help_text='Usuario que creó el VUCEM', on_delete=django.db.models.deletion.CASCADE, related_name='vucems_created', to=settings.AUTH_USER_MODEL)),
('organizacion', models.ForeignKey(help_text='Organización a la que pertenece el VUCEM', on_delete=django.db.models.deletion.CASCADE, related_name='vucems', to='organization.organizacion')),
('updated_by', models.ForeignKey(blank=True, help_text='Usuario que actualizó el VUCEM', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='vucems_updated', to=settings.AUTH_USER_MODEL)),
],
options={
'verbose_name': 'VUCEM',
'verbose_name_plural': 'VUCEMs',
'db_table': 'vucem',
},
),
]

View File

@@ -0,0 +1,21 @@
# Generated by Django 5.2.3 on 2025-07-14 17:39
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('vucem', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.AddField(
model_name='vucem',
name='user',
field=models.ForeignKey(blank=True, help_text='Usuario que creó el VUCEM', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='vucems', to=settings.AUTH_USER_MODEL),
),
]

View File

@@ -0,0 +1,17 @@
# Generated by Django 5.2.3 on 2025-07-14 17:40
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('vucem', '0002_vucem_user'),
]
operations = [
migrations.RemoveField(
model_name='vucem',
name='user',
),
]

View File

@@ -0,0 +1,33 @@
# Generated by Django 5.2.3 on 2025-07-14 17:41
import django.db.models.deletion
import uuid
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('vucem', '0003_remove_vucem_user'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='UsuarioImportador',
fields=[
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('rfc', models.CharField(help_text='RFC del usuario importador', max_length=13, unique=True)),
('created_at', models.DateTimeField(auto_now_add=True, help_text='Fecha de creación del registro')),
('updated_at', models.DateTimeField(auto_now=True, help_text='Fecha de última actualización del registro')),
('user', models.ForeignKey(help_text='Usuario de la plataforma asociado al importador', on_delete=django.db.models.deletion.CASCADE, related_name='usuarios_importadores', to=settings.AUTH_USER_MODEL)),
('vucem', models.ForeignKey(help_text='VUCEM asociado al usuario importador', on_delete=django.db.models.deletion.CASCADE, related_name='usuarios_importadores', to='vucem.vucem')),
],
options={
'verbose_name': 'Usuario Importador',
'verbose_name_plural': 'Usuarios Importadores',
'db_table': 'usuario_importador',
},
),
]

View File

@@ -0,0 +1,20 @@
# Generated by Django 5.2.3 on 2025-07-14 17:42
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('organization', '0002_remove_organizacion_membretado_and_more'),
('vucem', '0004_usuarioimportador'),
]
operations = [
migrations.AddField(
model_name='usuarioimportador',
name='organizacion',
field=models.ForeignKey(blank=True, help_text='Organización a la que pertenece el usuario importador', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='usuarios_importadores', to='organization.organizacion'),
),
]

View File

@@ -0,0 +1,25 @@
# Generated by Django 5.2.3 on 2025-07-23 22:12
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('vucem', '0005_usuarioimportador_organizacion'),
]
operations = [
migrations.AddField(
model_name='vucem',
name='cer',
field=models.FileField(default='', help_text='Certificado de VUCEM', upload_to='vucem_certs/'),
preserve_default=False,
),
migrations.AddField(
model_name='vucem',
name='key',
field=models.FileField(default='', help_text='Llave privada de VUCEM', upload_to='vucem_keys/'),
preserve_default=False,
),
]

View File

@@ -0,0 +1,19 @@
# Generated by Django 5.2.3 on 2025-07-31 16:20
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('vucem', '0006_vucem_cer_vucem_key'),
]
operations = [
migrations.AddField(
model_name='vucem',
name='efirma',
field=models.CharField(default='', help_text='E-Firma de VUCEM', max_length=100),
preserve_default=False,
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.2.3 on 2025-07-31 16:21
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('vucem', '0007_vucem_efirma'),
]
operations = [
migrations.AlterField(
model_name='vucem',
name='efirma',
field=models.CharField(blank=True, help_text='E-Firma de VUCEM', max_length=100, null=True),
),
]

View File

@@ -0,0 +1,17 @@
# Generated by Django 5.2.3 on 2025-08-12 14:01
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('vucem', '0008_alter_vucem_efirma'),
]
operations = [
migrations.RemoveField(
model_name='usuarioimportador',
name='user',
),
]

View File

@@ -0,0 +1,26 @@
# Generated by Django 5.2.3 on 2025-08-12 18:03
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('organization', '0002_remove_organizacion_membretado_and_more'),
('vucem', '0009_remove_usuarioimportador_user'),
]
operations = [
migrations.RenameModel(
old_name='UsuarioImportador',
new_name='CredencialesImportador',
),
migrations.AlterModelOptions(
name='credencialesimportador',
options={'verbose_name': 'Credencial Importador', 'verbose_name_plural': 'Credenciales Importadores'},
),
migrations.AlterModelTable(
name='credencialesimportador',
table='credenciales_importadores',
),
]

View File

@@ -0,0 +1,20 @@
# Generated by Django 5.2.3 on 2025-08-16 16:13
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('customs', '0010_alter_pedimento_contribuyente'),
('vucem', '0010_rename_usuarioimportador_credencialesimportador_and_more'),
]
operations = [
migrations.AlterField(
model_name='credencialesimportador',
name='rfc',
field=models.ForeignKey(help_text='RFC del importador asociado al usuario importador', on_delete=django.db.models.deletion.CASCADE, related_name='usuarios_importadores', to='customs.importador'),
),
]

View File

60
api/vucem/models.py Normal file
View File

@@ -0,0 +1,60 @@
from django.db import models
from api.cuser.models import CustomUser
import uuid
# Create your models here.
class Vucem(models.Model):
"""
Modelo para almacenar información de VUCEM.
"""
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
created_by = models.ForeignKey('cuser.CustomUser', on_delete=models.CASCADE, related_name='vucems_created', help_text="Usuario que creó el VUCEM")
updated_by = models.ForeignKey('cuser.CustomUser', on_delete=models.CASCADE, related_name='vucems_updated', null=True, blank=True, help_text="Usuario que actualizó el VUCEM")
organizacion = models.ForeignKey('organization.Organizacion', on_delete=models.CASCADE, related_name='vucems', help_text="Organización a la que pertenece el VUCEM")
usuario = models.CharField(max_length=100, unique=True, help_text="Usuario de VUCEM")
password = models.CharField(max_length=100, help_text="Contraseña de VUCEM")
patente = models.CharField(max_length=100, unique=True, help_text="Patente de VUCEM")
efirma = models.CharField(max_length=100, blank=True, null=True,help_text="E-Firma de VUCEM")
key = models.FileField(upload_to='vucem_keys/', help_text="Llave privada de VUCEM")
cer = models.FileField(upload_to='vucem_certs/', help_text="Certificado de VUCEM")
is_importador = models.BooleanField(default=False, help_text="Indica si es importador")
acusecove = models.BooleanField(default=False, help_text="Indica si generara acusecove")
acuseedocument = models.BooleanField(default=False, help_text="Indica si generara acusee edocumento")
is_active = models.BooleanField(default=True, help_text="Indica si el registro está activo")
created_at = models.DateTimeField(auto_now_add=True, help_text="Fecha de creación del registro")
updated_at = models.DateTimeField(auto_now=True, help_text="Fecha de última actualización del registro")
class Meta:
verbose_name = 'VUCEM'
verbose_name_plural = 'VUCEMs'
db_table = 'vucem'
def __str__(self):
return self.organizacion.nombre + ' - ' + self.usuario
class CredencialesImportador(models.Model):
"""
Modelo para almacenar información de usuarios importadores.
"""
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
organizacion = models.ForeignKey('organization.Organizacion', on_delete=models.CASCADE, related_name='usuarios_importadores', help_text="Organización a la que pertenece el usuario importador", blank=True, null=True)
vucem = models.ForeignKey(Vucem, on_delete=models.CASCADE, related_name='usuarios_importadores', help_text="VUCEM asociado al usuario importador")
rfc = models.ForeignKey('customs.Importador', on_delete=models.CASCADE, related_name='usuarios_importadores', help_text="RFC del importador asociado al usuario importador")
created_at = models.DateTimeField(auto_now_add=True, help_text="Fecha de creación del registro")
updated_at = models.DateTimeField(auto_now=True, help_text="Fecha de última actualización del registro")
class Meta:
verbose_name = 'Credencial Importador'
verbose_name_plural = 'Credenciales Importadores'
db_table = 'credenciales_importadores'
def __str__(self):
return self.organizacion.nombre + ' - ' + str(self.vucem.usuario)

40
api/vucem/serializers.py Normal file
View File

@@ -0,0 +1,40 @@
from rest_framework import serializers
from .models import Vucem, CredencialesImportador
class VucemSerializer(serializers.ModelSerializer):
importadores = serializers.SerializerMethodField()
class Meta:
model = Vucem
fields = '__all__'
read_only_fields = ('created_at', 'updated_at', 'organizacion', 'created_by', 'updated_by')
def get_importadores(self, obj):
# Importar aquí para evitar importación circular
from api.customs.serializers import ImportadorSerializer
return [ImportadorSerializer(cred.rfc).data for cred in obj.usuarios_importadores.all()]
class CredencialesImportadorSimpleSerializer(serializers.ModelSerializer):
class Meta:
model = CredencialesImportador
fields = ('__all__')
read_only_fields = ('updated_at',)
class CredencialesImportadorSerializer(serializers.ModelSerializer):
class Meta:
model = CredencialesImportador
fields = '__all__'
read_only_fields = ('updated_at',)
def to_representation(self, instance):
representation = super().to_representation(instance)
representation['vucem'] = VucemSerializer(instance.vucem).data
return representation

3
api/vucem/tests.py Normal file
View File

@@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

15
api/vucem/urls.py Normal file
View File

@@ -0,0 +1,15 @@
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from .views import VucemView, CredencialesImportadorViewSet
# Create a router and register your viewsets with it
router = DefaultRouter()
# Register your viewsets with the router here
router.register(r'vucem', VucemView, basename='Vucem')
router.register(r'usuario-importador', CredencialesImportadorViewSet, basename='CredencialesImportador')
urlpatterns = [
path('', include(router.urls)),
]

182
api/vucem/views.py Normal file
View File

@@ -0,0 +1,182 @@
from django.shortcuts import render
from rest_framework import viewsets
from rest_framework.pagination import PageNumberPagination
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework.filters import SearchFilter, OrderingFilter
from rest_framework.permissions import IsAuthenticated
from rest_framework.decorators import action
from rest_framework.response import Response
from django.http import FileResponse, Http404
from .serializers import VucemSerializer, CredencialesImportadorSerializer, CredencialesImportadorSimpleSerializer
from rest_framework import serializers
# Serializer para update donde key y cer no son requeridos
class VucemUpdateSerializer(VucemSerializer):
key = serializers.FileField(required=False, allow_null=True)
cer = serializers.FileField(required=False, allow_null=True)
class Meta(VucemSerializer.Meta):
fields = VucemSerializer.Meta.fields
from .models import Vucem, CredencialesImportador
from core.permissions import IsSameOrganizationDeveloper
from rest_framework import mixins
from core.permissions import (
IsSameOrganization,
IsSameOrganizationDeveloper,
IsSameOrganizationAndAdmin,
IsSuperUser,
IsSameOrganizationAndInAllowedGroups
)
class CustomVucemPagination(PageNumberPagination):
"""
Paginación personalizada para VUCEM
"""
page_size = None # Sin paginación por defecto
page_size_query_param = 'page_size'
max_page_size = 1000
page_query_param = 'page'
def paginate_queryset(self, queryset, request, view=None):
page_size = request.query_params.get(self.page_size_query_param)
if page_size is None:
return None
return super().paginate_queryset(queryset, request, view)
# Create your views here.
class VucemView(viewsets.ModelViewSet):
permission_classes = [IsAuthenticated , (IsSuperUser | IsSameOrganization | IsSameOrganizationAndAdmin | IsSameOrganizationDeveloper )]
queryset = Vucem.objects.all()
pagination_class = CustomVucemPagination
filterset_fields = ['organizacion', 'patente', 'usuario', 'is_importador', 'acusecove', 'acuseedocument', 'is_active']
search_fields = ['usuario', 'patente']
ordering_fields = ['created_at', 'updated_at', 'usuario', 'patente']
ordering = ['-created_at']
def get_serializer_class(self):
if self.action in ['update', 'partial_update']:
return VucemUpdateSerializer
return VucemSerializer
def get_permissions(self):
if self.action in ['create', 'update', 'partial_update', 'destroy']:
return [IsAuthenticated(), IsSameOrganizationAndInAllowedGroups()]
return super().get_permissions()
def get_queryset(self):
# Verificar que el usuario esté autenticado y tenga organización
if not self.request.user.is_authenticated:
return self.queryset.none()
queryset = self.queryset
if self.request.user.is_superuser:
queryset = queryset.all()
elif not hasattr(self.request.user, 'organizacion') or not self.request.user.organizacion:
return queryset.none()
elif self.request.user.groups.filter(name='Importador').exists():
queryset = queryset.filter(organizacion=self.request.user.organizacion, usuario=self.request.user.rfc)
else:
queryset = queryset.filter(organizacion=self.request.user.organizacion)
# Filtro por importador (RFC)
importador_rfc = self.request.query_params.get('importador')
if importador_rfc:
queryset = queryset.filter(usuarios_importadores__rfc__rfc=importador_rfc).distinct()
return queryset
def perform_create(self, serializer):
if not self.request.user.is_authenticated or not hasattr(self.request.user, 'organizacion'):
raise ValueError("El usuario debe estar autenticado y tener una organización asignada.")
if self.request.user.is_superuser:
serializer.save(created_by=self.request.user, updated_by=self.request.user)
return
else:
serializer.save(
organizacion=self.request.user.organizacion,
created_by=self.request.user,
updated_by=self.request.user
)
return
def perform_update(self, serializer):
if not self.request.user.is_authenticated or not hasattr(self.request.user, 'organizacion'):
raise ValueError("El usuario debe estar autenticado y tener una organización asignada.")
instance = self.get_object()
if self.request.user.is_superuser:
serializer.save(
created_by=instance.created_by,
updated_by=self.request.user
)
return
else:
serializer.save(
organizacion=self.request.user.organizacion,
created_by=instance.created_by,
updated_by=self.request.user
)
return
@action(detail=True, methods=["get"], permission_classes=[IsAuthenticated])
def download_cer(self, request, pk=None):
"""
Descarga directa del archivo cer.
"""
vucem = self.get_object()
if not vucem.cer:
return Response({"detail": "No hay archivo cer disponible."}, status=404)
response = FileResponse(vucem.cer.open('rb'), as_attachment=True, filename=vucem.cer.name.split('/')[-1])
return response
@action(detail=True, methods=["get"], permission_classes=[IsAuthenticated])
def download_key(self, request, pk=None):
"""
Descarga directa del archivo key.
"""
vucem = self.get_object()
if not vucem.key:
return Response({"detail": "No hay archivo key disponible."}, status=404)
response = FileResponse(vucem.key.open('rb'), as_attachment=True, filename=vucem.key.name.split('/')[-1])
return response
class CredencialesImportadorViewSet(viewsets.ModelViewSet):
permission_classes = [IsAuthenticated]
queryset = CredencialesImportador.objects.all()
serializer_class = CredencialesImportadorSimpleSerializer
filterset_fields = ['organizacion', 'vucem', 'rfc']
search_fields = ['rfc']
ordering_fields = ['created_at', 'updated_at', 'rfc']
ordering = ['-created_at']
my_tags = ['Credenciales por Importador']
def get_permissions(self):
if self.action in ['create', 'update', 'partial_update', 'destroy']:
return [IsAuthenticated()]
return super().get_permissions()
def get_queryset(self):
if self.request.user.is_superuser:
# Si es superusuario, devolver todos los registros
return self.queryset.all()
# Verificar que el usuario esté autenticado y tenga organización
if not self.request.user.is_authenticated or not hasattr(self.request.user, 'organizacion'):
return self.queryset.none()
queryset = self.queryset.filter(organizacion=self.request.user.organizacion)
return queryset
def perform_create(self, serializer):
if not self.request.user.is_authenticated or not hasattr(self.request.user, 'organizacion'):
raise ValueError("El usuario debe estar autenticado y tener una organización asignada.")
serializer.save(organizacion=self.request.user.organizacion)
return