· 6 months ago · Mar 20, 2025, 12:05 PM
1# Django Core
2from django.shortcuts import render, redirect, get_object_or_404
3from django.http import HttpResponse, JsonResponse, HttpResponseNotAllowed
4from django.core.paginator import Paginator
5from django.db.models import Q
6from django.views.generic import ListView
7from django.views.decorators.csrf import csrf_exempt
8from django.views.decorators.http import require_http_methods
9from django.contrib import messages
10from django.views import View
11from .forms import PagoForm
12from datetime import date, datetime
13from django.utils.timezone import now
14from django.db.models import Exists, OuterRef, Sum, Count
15import json
16from django.core.files.storage import FileSystemStorage
17import openpyxl
18#import pandas as pd
19import re
20from django.utils.decorators import method_decorator
21from django.db import models
22
23
24
25# Autenticación
26from django.contrib.auth import authenticate, login, logout
27from django.contrib.auth.decorators import login_required, user_passes_test
28from .models import Cliente, DireccionInstalacion, Pago
29
30
31# Formularios locales
32from .forms import (
33 ClienteForm,
34 ContratoForm,
35 DireccionFormSet,
36 DireccionForm,
37 ServicioForm
38)
39
40# Modelos locales
41from .models import (
42 Cliente,
43 Contrato,
44 Servicio,
45 Usuario,
46 Rol,
47 Empleado,
48 Zona,
49 DireccionInstalacion # Usa la clase actualizada
50)
51
52# Librerías estándar
53import random
54import json
55import traceback
56
57
58
59def login_view(request):
60 if request.method == "POST":
61 username = request.POST['username']
62 password = request.POST['password']
63 user = authenticate(request, username=username, password=password)
64 if user is not None:
65 login(request, user)
66 return redirect('dashboard') # Redirigir al dashboard si el login es exitoso
67 else:
68 messages.error(request, "Usuario o contraseña incorrectos")
69 return render(request, 'usuarios/login.html')
70
71def logout_view(request):
72 logout(request)
73 return redirect('login') # Redirigir al login después de cerrar sesión
74
75@login_required
76def dashboard(request):
77 return render(request, 'usuarios/dashboard.html')
78
79### CRUD USUARIOS ###
80
81@login_required
82def lista_usuarios(request):
83 usuarios = Usuario.objects.all()
84 roles = Rol.objects.all() # ✅ Asegurar que los roles estén disponibles en la plantilla
85 return render(request, 'usuarios/lista_usuarios.html', {'usuarios': usuarios, 'roles': roles})
86
87@login_required
88def editar_usuario(request, user_id):
89 usuario = get_object_or_404(Usuario, id=user_id)
90
91 if request.method == 'POST':
92 usuario.username = request.POST['username']
93 usuario.first_name = request.POST['first_name']
94 usuario.last_name = request.POST['last_name']
95 usuario.email = request.POST['email']
96 usuario.rol_id = request.POST.get('rol')
97 usuario.estatus = 'estatus' in request.POST # Checkbox activo/inactivo
98 usuario.save()
99 messages.success(request, "Usuario actualizado correctamente.")
100 return redirect('lista_usuarios')
101
102 roles = Rol.objects.all()
103 return render(request, 'usuarios/editar_usuario.html', {'usuario': usuario, 'roles': roles})
104
105@login_required
106def eliminar_usuario(request, user_id):
107 usuario = get_object_or_404(Usuario, id=user_id)
108 usuario.delete()
109 messages.success(request, "Usuario eliminado correctamente.")
110 return redirect('lista_usuarios')
111
112@login_required
113def crear_usuario(request):
114 if request.method == "POST":
115 username = request.POST.get('username').strip()
116 first_name = request.POST.get('first_name').strip()
117 last_name = request.POST.get('last_name').strip()
118 email = request.POST.get('email').strip()
119 password = request.POST.get('password')
120 rol_id = request.POST.get('rol')
121
122 # Validar que no haya campos vacíos
123 if not username or not first_name or not last_name or not email or not password or not rol_id:
124 messages.error(request, "Todos los campos son obligatorios.")
125 return redirect('lista_usuarios')
126
127 # Validar si el usuario ya existe
128 if Usuario.objects.filter(username=username).exists():
129 messages.error(request, "El nombre de usuario ya está en uso.")
130 return redirect('lista_usuarios')
131
132 # Verificar si el rol existe en la base de datos
133 try:
134 rol = Rol.objects.get(id=rol_id)
135 except Rol.DoesNotExist:
136 messages.error(request, "El rol seleccionado no es válido.")
137 return redirect('lista_usuarios')
138
139 # Crear usuario y cifrar la contraseña
140 nuevo_usuario = Usuario(
141 username=username,
142 first_name=first_name,
143 last_name=last_name,
144 email=email,
145 rol=rol
146 )
147 nuevo_usuario.set_password(password) # 🔐 Cifra la contraseña
148 nuevo_usuario.save()
149
150 messages.success(request, f"Usuario '{username}' creado correctamente.")
151 return redirect('lista_usuarios')
152
153 # Si el método no es POST, regresar la lista de usuarios
154 usuarios = Usuario.objects.all()
155 roles = Rol.objects.all()
156 return render(request, 'usuarios/lista_usuarios.html', {'usuarios': usuarios, 'roles': roles})
157
158
159### CRUD EMPLEADOS ###
160
161@login_required
162def lista_empleados(request):
163 empleados = Empleado.objects.all()
164 return render(request, 'usuarios/lista_empleados.html', {'empleados': empleados})
165
166@login_required
167def crear_empleado(request):
168 if request.method == "POST":
169 nombre = request.POST['nombre']
170 apellido = request.POST['apellido']
171 dni = request.POST['dni']
172 rol_id = request.POST.get('rol')
173 rol = Rol.objects.get(id=rol_id)
174
175 Empleado.objects.create(nombre=nombre, apellido=apellido, dni=dni, rol=rol)
176 messages.success(request, "Empleado agregado correctamente.")
177 return redirect('lista_empleados')
178
179 roles = Rol.objects.all()
180 return render(request, 'usuarios/crear_empleado.html', {'roles': roles})
181
182@login_required
183def editar_empleado(request, emp_id):
184 empleado = get_object_or_404(Empleado, id=emp_id)
185
186 if request.method == "POST":
187 empleado.nombre = request.POST['nombre']
188 empleado.apellido = request.POST['apellido']
189 empleado.dni = request.POST['dni']
190 empleado.rol_id = request.POST.get('rol')
191 empleado.save()
192 messages.success(request, "Empleado actualizado correctamente.")
193 return redirect('lista_empleados')
194
195 roles = Rol.objects.all()
196 return render(request, 'usuarios/editar_empleado.html', {'empleado': empleado, 'roles': roles})
197
198@login_required
199def eliminar_empleado(request, emp_id):
200 empleado = get_object_or_404(Empleado, id=emp_id)
201 empleado.delete()
202 messages.success(request, "Empleado eliminado correctamente.")
203 return redirect('lista_empleados')
204
205### CRUD ROLES ###
206
207@login_required
208def lista_roles(request):
209 roles = Rol.objects.all()
210 return render(request, 'usuarios/lista_roles.html', {'roles': roles})
211
212@login_required
213def crear_rol(request):
214 if request.method == "POST":
215 nombre = request.POST['nombre']
216 Rol.objects.create(nombre=nombre)
217 messages.success(request, "Rol creado correctamente.")
218 return redirect('lista_roles')
219
220 return render(request, 'usuarios/crear_rol.html')
221
222@login_required
223def editar_rol(request, rol_id):
224 rol = get_object_or_404(Rol, id=rol_id)
225
226 if request.method == "POST":
227 rol.nombre = request.POST['nombre']
228 rol.save()
229 messages.success(request, "Rol actualizado correctamente.")
230 return redirect('lista_roles')
231
232 return render(request, 'usuarios/editar_rol.html', {'rol': rol})
233
234@login_required
235def eliminar_rol(request, rol_id):
236 rol = get_object_or_404(Rol, id=rol_id)
237 rol.delete()
238 messages.success(request, "Rol eliminado correctamente.")
239 return redirect('lista_roles')
240
241
242@login_required
243def editar_usuario(request, user_id):
244 usuario = get_object_or_404(Usuario, id=user_id)
245
246 if request.method == "POST":
247 usuario.username = request.POST.get('username').strip()
248 usuario.first_name = request.POST.get('first_name').strip()
249 usuario.last_name = request.POST.get('last_name').strip()
250 usuario.email = request.POST.get('email').strip()
251 rol_id = request.POST.get('rol')
252 usuario.estatus = 'estatus' in request.POST # Checkbox para activar/desactivar usuario
253
254 # Validar si el usuario ya existe con otro ID
255 if Usuario.objects.exclude(id=user_id).filter(username=usuario.username).exists():
256 messages.error(request, "El nombre de usuario ya está en uso por otro usuario.")
257 return redirect('lista_usuarios')
258
259 # Verificar si el rol existe
260 try:
261 usuario.rol = Rol.objects.get(id=rol_id)
262 except Rol.DoesNotExist:
263 messages.error(request, "El rol seleccionado no es válido.")
264 return redirect('lista_usuarios')
265
266 usuario.save()
267 messages.success(request, f"Usuario '{usuario.username}' actualizado correctamente.")
268 return redirect('lista_usuarios')
269
270 roles = Rol.objects.all()
271 return render(request, 'usuarios/editar_usuario.html', {'usuario': usuario, 'roles': roles})
272
273
274@login_required
275def eliminar_usuario(request, user_id):
276 usuario = get_object_or_404(Usuario, id=user_id)
277 usuario.delete()
278 messages.success(request, f"Usuario '{usuario.username}' eliminado correctamente.")
279 return redirect('lista_usuarios')
280
281
282# Función para verificar si el usuario es administrador
283def es_admin(user):
284 return user.is_authenticated and user.is_superuser
285
286### RESTRINGIR VISTAS A ADMINISTRADORES ###
287
288@login_required
289@user_passes_test(es_admin, login_url='/dashboard/') # Redirigir si no es admin
290def lista_usuarios(request):
291 usuarios = Usuario.objects.all()
292 roles = Rol.objects.all()
293 return render(request, 'usuarios/lista_usuarios.html', {'usuarios': usuarios, 'roles': roles})
294
295@login_required
296@user_passes_test(es_admin, login_url='/dashboard/')
297def crear_usuario(request):
298 if request.method == "POST":
299 username = request.POST.get('username').strip()
300 first_name = request.POST.get('first_name').strip()
301 last_name = request.POST.get('last_name').strip()
302 email = request.POST.get('email').strip()
303 password = request.POST.get('password')
304 rol_id = request.POST.get('rol')
305
306 if Usuario.objects.filter(username=username).exists():
307 messages.error(request, "El nombre de usuario ya está en uso.")
308 return redirect('lista_usuarios')
309
310 try:
311 rol = Rol.objects.get(id=rol_id)
312 except Rol.DoesNotExist:
313 messages.error(request, "El rol seleccionado no es válido.")
314 return redirect('lista_usuarios')
315
316 nuevo_usuario = Usuario(
317 username=username,
318 first_name=first_name,
319 last_name=last_name,
320 email=email,
321 rol=rol
322 )
323 nuevo_usuario.set_password(password)
324 nuevo_usuario.save()
325
326 messages.success(request, f"Usuario '{username}' creado correctamente.")
327 return redirect('lista_usuarios')
328
329 return redirect('lista_usuarios')
330
331@login_required
332@user_passes_test(es_admin, login_url='/dashboard/')
333def eliminar_usuario(request, user_id):
334 usuario = get_object_or_404(Usuario, id=user_id)
335 usuario.delete()
336 messages.success(request, f"Usuario '{usuario.username}' eliminado correctamente.")
337 return redirect('lista_usuarios')
338
339@login_required
340@user_passes_test(es_admin, login_url='/dashboard/')
341def lista_roles(request):
342 roles = Rol.objects.all()
343 return render(request, 'usuarios/lista_roles.html', {'roles': roles})
344
345@login_required
346@user_passes_test(es_admin, login_url='/dashboard/')
347def crear_rol(request):
348 if request.method == "POST":
349 nombre = request.POST['nombre']
350 Rol.objects.create(nombre=nombre)
351 messages.success(request, "Rol creado correctamente.")
352 return redirect('lista_roles')
353
354 return render(request, 'usuarios/crear_rol.html')
355
356@login_required
357@user_passes_test(es_admin, login_url='/dashboard/')
358def eliminar_rol(request, rol_id):
359 rol = get_object_or_404(Rol, id=rol_id)
360 rol.delete()
361 messages.success(request, "Rol eliminado correctamente.")
362 return redirect('lista_roles')
363
364
365def lista_zonas(request):
366 zonas = Zona.objects.all()
367 return render(request, "usuarios/lista_zonas.html", {"zonas": zonas})
368
369def crear_zona(request):
370 if request.method == "POST":
371 nombre = request.POST["nombre"]
372 Zona.objects.create(nombre=nombre)
373 messages.success(request, "Zona creada correctamente.")
374 return redirect("lista_zonas")
375 return redirect("lista_zonas")
376
377def editar_zona(request, zona_id):
378 zona = get_object_or_404(Zona, id=zona_id)
379 if request.method == "POST":
380 zona.nombre = request.POST["nombre"]
381 zona.save()
382 messages.success(request, "Zona actualizada correctamente.")
383 return redirect("lista_zonas")
384 return redirect("lista_zonas")
385
386def eliminar_zona(request, zona_id):
387 zona = get_object_or_404(Zona, id=zona_id)
388 zona.delete()
389 messages.success(request, "Zona eliminada correctamente.")
390 return redirect("lista_zonas")
391
392
393def get_zonas(request):
394 zonas = list(Zona.objects.values('id', 'nombre'))
395 return JsonResponse({'zonas': zonas})
396
397
398
399# Lista todos los clientes
400class ClienteListView(ListView):
401 model = Cliente
402 template_name = 'usuarios/cliente_list.html'
403 context_object_name = 'clientes'
404 paginate_by = 10 # Muestra de 10 en 10
405
406 def get_queryset(self):
407 queryset = super().get_queryset()
408 buscar = self.request.GET.get('buscar', '')
409 if buscar:
410 queryset = queryset.filter(
411 Q(nombre__icontains=buscar) |
412 Q(apellido_paterno__icontains=buscar) |
413 Q(apellido_materno__icontains=buscar) |
414 Q(numero_documento__icontains=buscar)
415 )
416 return queryset.order_by('nombre')
417
418 def get_context_data(self, **kwargs):
419 context = super().get_context_data(**kwargs)
420 context['buscar'] = self.request.GET.get('buscar', '')
421 return context
422
423# Crea un nuevo cliente con múltiples direcciones
424def crear_cliente(request):
425 if request.method == 'POST':
426 form = ClienteForm(request.POST)
427 formset = DireccionFormSet(request.POST)
428 if form.is_valid() and formset.is_valid():
429 cliente = form.save()
430 direcciones = formset.save(commit=False)
431 for direccion in direcciones:
432 direccion.cliente = cliente
433 direccion.save()
434 return redirect('cliente_list')
435 else:
436 form = ClienteForm()
437 formset = DireccionFormSet()
438
439 return render(request, 'usuarios/cliente_form.html', {
440 'form': form,
441 'formset': formset,
442 })
443
444
445def editar_cliente(request, pk):
446 cliente = get_object_or_404(Cliente, pk=pk)
447
448 if request.method == "POST":
449 form = ClienteForm(request.POST, instance=cliente)
450 formset = DireccionFormSet(request.POST, instance=cliente)
451
452 if form.is_valid() and formset.is_valid():
453 form.save()
454 formset.save()
455 print("✅ Cliente guardado correctamente")
456 return redirect('cliente_list')
457
458 else:
459 print("❌ Error en el formulario:")
460 print(form.errors)
461 print(formset.errors)
462
463 else:
464 form = ClienteForm(instance=cliente)
465 formset = DireccionFormSet(instance=cliente)
466
467 return render(request, 'usuarios/cliente_form.html', {'form': form, 'formset': formset})
468
469
470
471
472
473def obtener_direcciones_cliente(request, cliente_id):
474 try:
475 cliente = Cliente.objects.get(id=cliente_id)
476 data = {'direccion': cliente.direccion} # Asegúrate de que 'direccion' sea el campo correcto
477 except Cliente.DoesNotExist:
478 data = {'direccion': ''} # Si el cliente no existe, devuelve una dirección vacía
479 return JsonResponse(data)
480
481
482def crear_contrato(request):
483 if request.method == 'POST':
484 cliente_id = request.POST.get('cliente')
485 form = ContratoForm(request.POST, cliente_id=cliente_id)
486 if form.is_valid():
487 contrato = form.save(commit=False)
488 contrato.total = sum(servicio.precio for servicio in form.cleaned_data['servicios'])
489 contrato.save()
490 form.save_m2m()
491 messages.success(request, "Contrato creado exitosamente.")
492 return redirect('lista_contratos')
493 else:
494 form = ContratoForm()
495
496 return render(request, 'usuarios/contrato_form.html', {'form': form})
497
498
499
500# Editar un contrato existente
501def editar_contrato(request, pk):
502 contrato = get_object_or_404(Contrato, pk=pk)
503 if request.method == 'POST':
504 cliente_id = contrato.cliente.id
505 form = ContratoForm(request.POST, instance=contrato, cliente_id=cliente_id)
506 if form.is_valid():
507 contrato = form.save(commit=False)
508 contrato.total = sum(servicio.precio for servicio in form.cleaned_data['servicios'])
509 contrato.save()
510 form.save_m2m()
511 messages.success(request, "Contrato actualizado correctamente.")
512 return redirect('lista_contratos')
513 else:
514 form = ContratoForm(instance=contrato, cliente_id=contrato.cliente.id)
515
516 return render(request, 'usuarios/contrato_form.html', {'form': form})
517
518def lista_contratos(request):
519 contratos_list = Contrato.objects.all().order_by('-fecha_contratacion') # Ordenar por fecha
520 paginator = Paginator(contratos_list, 5) # 5 contratos por página
521
522 page_number = request.GET.get('page')
523 contratos = paginator.get_page(page_number)
524
525 return render(request, 'usuarios/lista_contratos.html', {'contratos': contratos})
526
527
528def eliminar_contrato(request, pk):
529 contrato = get_object_or_404(Contrato, pk=pk)
530 contrato.delete()
531 messages.success(request, "Contrato eliminado correctamente.")
532 return redirect('lista_contratos') # Asegúrate de que esta vista existe en `urls.py`
533
534
535
536def lista_servicios(request):
537 servicios = Servicio.objects.all()
538 return render(request, 'usuarios/lista_servicios.html', {'servicios': servicios})
539
540def agregar_servicio(request):
541 if request.method == 'POST':
542 form = ServicioForm(request.POST)
543 if form.is_valid():
544 form.save()
545 return redirect('servicios')
546 else:
547 form = ServicioForm()
548 return render(request, 'usuarios/agregar_servicio.html', {'form': form})
549
550def editar_servicio(request, servicio_id):
551 servicio = get_object_or_404(Servicio, pk=servicio_id)
552 if request.method == 'POST':
553 form = ServicioForm(request.POST, instance=servicio)
554 if form.is_valid():
555 form.save()
556 return redirect('servicios')
557 else:
558 form = ServicioForm(instance=servicio)
559 return render(request, 'usuarios/editar_servicio.html', {'form': form, 'servicio': servicio})
560
561
562def eliminar_servicio(request, servicio_id):
563 servicio = get_object_or_404(Servicio, pk=servicio_id)
564 try:
565 servicio.delete()
566 messages.success(request, "Servicio eliminado correctamente.")
567 except Exception as e:
568 messages.error(request, f"No se pudo eliminar el servicio: {e}")
569
570 return redirect('servicios')
571# API para obtener la lista de clientes
572def api_clientes(request):
573 clientes = Cliente.objects.all().values("id", "numero_documento", "nombre")
574 return JsonResponse(list(clientes), safe=False)
575
576# API para obtener direcciones del cliente seleccionado
577def api_direcciones_cliente(request, cliente_id):
578 direcciones = DireccionInstalacion.objects.filter(cliente_id=cliente_id).values("id", "direccion", "zona__nombre")
579 return JsonResponse(list(direcciones), safe=False)
580
581
582def buscar_cliente(request):
583 query = request.GET.get('query', '')
584
585 if query:
586 clientes = Cliente.objects.filter(
587 Q(nombre__icontains=query) | Q(numero_documento__icontains=query)
588 ).prefetch_related('contrato_set')
589
590 data = []
591 for cliente in clientes:
592 contrato = cliente.contrato_set.first() # Obtener el primer contrato si existe
593 servicios = contrato.servicios.all() if contrato else []
594 servicio_nombres = ", ".join([s.nombre for s in servicios]) if servicios else "Sin servicio"
595
596 data.append({
597 'id': cliente.id,
598 'nombre': cliente.nombre,
599 'direccion': contrato.direccion_instalacion.direccion if contrato and contrato.direccion_instalacion else "No registrada",
600 'telefono': cliente.telefono if cliente.telefono else "No registrado",
601 'servicio': servicio_nombres
602 })
603
604 return JsonResponse(data, safe=False)
605
606 return JsonResponse({'error': 'No se encontraron clientes'}, status=404)
607
608def pago_servicios(request):
609 return render(request, 'usuarios/pago_servicios.html')
610
611
612
613
614def eliminar_cliente(request, pk):
615 cliente = get_object_or_404(Cliente, pk=pk)
616 cliente.delete()
617 messages.success(request, 'Cliente eliminado correctamente.')
618 return redirect('cliente_list')
619
620
621
622
623class RegistrarPagoView(View):
624 def get(self, request, cliente_id):
625 cliente = get_object_or_404(Cliente, id=cliente_id)
626 contratos = Contrato.objects.filter(cliente=cliente)
627
628 # ✅ Verificar si el cliente tiene contratos antes de continuar
629 if not contratos.exists():
630 messages.error(request, f"El cliente {cliente.nombre} no tiene contratos asociados.")
631 return redirect('lista_pagos') # Redirige a la lista de pagos
632
633 form = PagoForm(cliente_id=cliente.id)
634 pagos_realizados = Pago.objects.filter(cliente=cliente).values_list('mes_pagado', flat=True)
635
636 # ✅ Crear estructura para mostrar estado de pagos en los meses del año
637 MESES_CHOICES = [
638 (1, "Enero"), (2, "Febrero"), (3, "Marzo"), (4, "Abril"),
639 (5, "Mayo"), (6, "Junio"), (7, "Julio"), (8, "Agosto"),
640 (9, "Septiembre"), (10, "Octubre"), (11, "Noviembre"), (12, "Diciembre")
641 ]
642
643 estado_pagos = [
644 {
645 "mes": nombre_mes,
646 "pagado": mes in pagos_realizados
647 }
648 for mes, nombre_mes in MESES_CHOICES
649 ]
650
651 return render(request, 'usuarios/registrar_pago.html', {
652 'cliente': cliente,
653 'contratos': contratos,
654 'form': form,
655 'pagos_realizados': pagos_realizados,
656 'estado_pagos': estado_pagos, # 👈 Enviamos la estructura de pagos al template
657 })
658
659 def post(self, request, cliente_id):
660 print("🔴 Datos recibidos en POST:", request.POST) # 👈 Imprime los datos enviados en la solicitud
661
662 cliente = get_object_or_404(Cliente, id=cliente_id)
663 contratos = Contrato.objects.filter(cliente=cliente)
664
665 # ✅ Verificar si el cliente tiene contratos antes de continuar
666 if not contratos.exists():
667 messages.error(request, f"El cliente {cliente.nombre} no tiene contratos asociados.")
668 return redirect('lista_pagos')
669
670 form = PagoForm(request.POST, cliente_id=cliente.id)
671
672 if form.is_valid():
673 pago = form.save(commit=False)
674 pago.cliente = cliente
675
676 # ✅ Asegurar que el contrato es válido antes de continuar
677 contrato_seleccionado = form.cleaned_data.get('contrato')
678 if contrato_seleccionado:
679 pago.monto = contrato_seleccionado.total # Usa el total del contrato
680 else:
681 messages.error(request, "Debe seleccionar un contrato válido.")
682 return self._recargar_pagina(request, cliente, contratos, form)
683
684 pago.save()
685 form.save_m2m()
686
687 # ✅ Agregar mensaje de éxito
688 messages.success(request, f'Pago registrado con éxito para {cliente.nombre} {cliente.apellido_paterno}.')
689
690 # ✅ Redirigir al historial de pagos
691 return redirect('historial_pagos', cliente_id=cliente.id)
692
693 # ❌ Si hay un error, mostrar el mensaje y recargar el formulario
694 print("⚠️ Errores en el formulario:", form.errors) # 👈 Imprime errores del formulario en consola
695 messages.error(request, "Hubo un error al registrar el pago. Revisa los datos ingresados.")
696 return self._recargar_pagina(request, cliente, contratos, form)
697
698def _recargar_pagina(self, request, cliente, contratos, form):
699 """ Función auxiliar para recargar la página con los datos actuales """
700 pagos_realizados = Pago.objects.filter(cliente=cliente).values_list('mes_pagado', flat=True)
701
702 MESES_CHOICES = [
703 (1, "Enero"), (2, "Febrero"), (3, "Marzo"), (4, "Abril"),
704 (5, "Mayo"), (6, "Junio"), (7, "Julio"), (8, "Agosto"),
705 (9, "Septiembre"), (10, "Octubre"), (11, "Noviembre"), (12, "Diciembre")
706 ]
707
708 estado_pagos = [
709 {
710 "mes": nombre_mes,
711 "pagado": mes in pagos_realizados
712 }
713 for mes, nombre_mes in MESES_CHOICES
714 ]
715
716 return render(request, 'usuarios/registrar_pago.html', {
717 'cliente': cliente,
718 'contratos': contratos,
719 'form': form,
720 'pagos_realizados': pagos_realizados,
721 'estado_pagos': estado_pagos, # 👈 Enviamos la estructura de pagos al template
722 })
723
724
725
726class HistorialPagosView(View):
727 template_name = 'usuarios/historial_pagos.html'
728
729 def get(self, request, cliente_id): # ✅ Ahora correctamente indentado
730 cliente = get_object_or_404(Cliente, id=cliente_id)
731 pagos = Pago.objects.filter(cliente=cliente).order_by('-fecha_pago')
732 return render(request, self.template_name, {'cliente': cliente, 'pagos': pagos})
733
734class DetalleClienteView(View):
735 template_name = 'usuarios/detalle_cliente.html'
736
737 def get(self, request, cliente_id):
738 cliente = get_object_or_404(Cliente, id=cliente_id)
739 return render(request, self.template_name, {'cliente': cliente})
740
741
742
743class BuscarClientePagoView(View):
744 template_name = 'usuarios/buscar_cliente_pago.html'
745
746 def get(self, request):
747 buscar = request.GET.get('buscar', '')
748 clientes = Cliente.objects.all()
749
750 if buscar:
751 clientes = clientes.filter(nombre__icontains=buscar)
752
753 # Verificar si el cliente ya pagó en el mes y año actual
754 mes_actual = now().month
755 anio_actual = now().year
756
757 clientes = clientes.annotate(
758 ha_pagado=Exists(
759 Pago.objects.filter(
760 cliente=OuterRef('pk'),
761 mes_pagado=mes_actual,
762 anio_pagado=anio_actual
763 )
764 )
765 )
766
767 return render(request, self.template_name, {
768 'clientes': clientes,
769 'buscar': buscar
770 })
771
772
773class HistorialPagosGeneralView(ListView):
774 model = Pago
775 template_name = "usuarios/historial_pagos_general.html"
776 context_object_name = "pagos"
777 paginate_by = 10 # Agregar paginación
778
779 def get_queryset(self):
780 queryset = Pago.objects.all().order_by('-fecha_pago')
781 buscar = self.request.GET.get("buscar")
782 if buscar:
783 queryset = queryset.filter(contrato__cliente__nombre__icontains=buscar)
784 return queryset
785
786def lista_contratos_pago(request):
787 contratos = Contrato.objects.all()
788 return render(request, 'usuarios/lista_pagos.html', {'contratos': contratos})
789
790
791class HistorialPagosClienteView(View):
792 def get(self, request, cliente_id):
793 cliente = get_object_or_404(Cliente, id=cliente_id)
794 pagos = Pago.objects.filter(cliente_id=cliente_id).order_by('-fecha_pago')
795
796 return render(request, 'usuarios/historial_pagos_cliente.html', {
797 'cliente': cliente,
798 'pagos': pagos
799 })
800
801
802# ... (otras importaciones y vistas)
803
804class DashboardView(View):
805 def get(self, request):
806 total_clientes = Cliente.objects.count()
807 total_contratos = Contrato.objects.count()
808 total_pagos = Pago.objects.count()
809 total_ingresos = Pago.objects.aggregate(total=Sum('monto'))['total'] or 0
810
811 # 📊 Datos para el gráfico de ingresos por mes
812 ingresos_por_mes = Pago.objects.values('mes_pagado').annotate(total=Sum('monto')).order_by('mes_pagado')
813 ingresos_dict = {mes['mes_pagado']: mes['total'] for mes in ingresos_por_mes}
814
815 meses = [
816 "Enero", "Febrero", "Marzo", "Abril", "Mayo", "Junio",
817 "Julio", "Agosto", "Septiembre", "Octubre", "Noviembre", "Diciembre"
818 ]
819 ingresos_data = [ingresos_dict.get(i, 0) for i in range(1, 13)]
820
821 print("📊 JSON de Meses:", json.dumps(meses, ensure_ascii=False))
822 print("📊 JSON de Ingresos:", json.dumps(ingresos_data, ensure_ascii=False))
823
824 return render(request, 'usuarios/dashboard.html', { # 👈 Ruta correcta
825 'total_clientes': total_clientes,
826 'total_contratos': total_contratos,
827 'total_pagos': total_pagos,
828 'total_ingresos': total_ingresos,
829 'meses_json': json.dumps(meses, ensure_ascii=False),
830 'ingresos_data_json': json.dumps(ingresos_data, ensure_ascii=False)
831 })
832
833# ... (otras vistas)
834
835def dashboard_data(request):
836 data = {
837 "total_clientes": Cliente.objects.count(),
838 "total_contratos": Contrato.objects.count(),
839 "total_pagos": Pago.objects.count(),
840 "total_ingresos": Pago.objects.aggregate(Sum('monto'))['monto__sum'] or 0
841 }
842 return JsonResponse(data)
843
844def dashboard_data(request):
845 data = {
846 "total_clientes": Cliente.objects.count(),
847 "total_contratos": Contrato.objects.count(),
848 "total_pagos": Pago.objects.count(),
849 "total_ingresos": Pago.objects.aggregate(Sum('monto'))['monto__sum']
850 }
851 return JsonResponse(data)
852
853
854 # Función para limpiar el número de documento
855def limpiar_numero_documento(numero_documento):
856 """Elimina puntos del número de documento pero mantiene el guion."""
857 return re.sub(r'\.', '', numero_documento)
858
859# Función para limpiar el número de documento
860def limpiar_numero_documento(numero_documento):
861 """Elimina puntos del número de documento pero mantiene el guion."""
862 return re.sub(r'\.', '', numero_documento)
863
864# Función para verificar si el usuario es administrador
865def es_admin(user):
866 return user.is_authenticated and user.is_staff # Solo administradores pueden acceder
867
868
869
870
871@user_passes_test(es_admin)
872def cargar_clientes_excel(request):
873 if request.method == "POST" and request.FILES.get("archivo"):
874 archivo = request.FILES["archivo"]
875
876 # Guardar archivo temporalmente
877 fs = FileSystemStorage()
878 filename = fs.save(archivo.name, archivo)
879 file_path = fs.path(filename)
880
881 try:
882 # Cargar el archivo Excel con openpyxl
883 workbook = openpyxl.load_workbook(file_path)
884 sheet = workbook.active # Obtener la primera hoja
885
886 # Definir las columnas necesarias (basado en el orden en el Excel)
887 columnas_requeridas = ["Número Documento", "Nombre", "Apellido Paterno", "Apellido Materno"]
888
889 # Obtener los encabezados de la primera fila
890 headers = [cell.value.strip() if cell.value else "" for cell in sheet[1]]
891
892 # Verificar si las columnas requeridas están en el archivo
893 for col in columnas_requeridas:
894 if col not in headers:
895 messages.error(request, f"El archivo no tiene la columna requerida: {col}. Columnas encontradas: {', '.join(headers)}")
896 return redirect("cargar_clientes_excel")
897
898 # Obtener los índices de las columnas
899 col_idx = {headers[i]: i + 1 for i in range(len(headers))}
900
901 # Leer datos desde la segunda fila en adelante
902 for row in sheet.iter_rows(min_row=2, values_only=True):
903 numero_documento = str(row[col_idx["Número Documento"] - 1]).strip() if row[col_idx["Número Documento"] - 1] else ""
904 nombre = str(row[col_idx["Nombre"] - 1]).strip() if row[col_idx["Nombre"] - 1] else ""
905 apellido_paterno = str(row[col_idx["Apellido Paterno"] - 1]).strip() if row[col_idx["Apellido Paterno"] - 1] else ""
906 apellido_materno = str(row[col_idx["Apellido Materno"] - 1]).strip() if row[col_idx["Apellido Materno"] - 1] else ""
907
908 numero_documento_limpio = limpiar_numero_documento(numero_documento)
909
910 if numero_documento_limpio and nombre:
911 Cliente.objects.update_or_create(
912 numero_documento=numero_documento_limpio,
913 defaults={
914 "nombre": nombre,
915 "apellido_paterno": apellido_paterno,
916 "apellido_materno": apellido_materno
917 }
918 )
919
920 messages.success(request, "Clientes cargados correctamente desde Excel.")
921 except Exception as e:
922 messages.error(request, f"Error al procesar el archivo: {str(e)}")
923
924 return redirect("cargar_clientes_excel")
925
926 return render(request, "usuarios/cargar_clientes.html")
927
928
929@user_passes_test(es_admin)
930def cargar_direcciones_excel(request):
931 if request.method == "POST" and request.FILES.get("archivo"):
932 archivo = request.FILES["archivo"]
933
934 try:
935 workbook = openpyxl.load_workbook(archivo)
936 sheet = workbook.active # Obtener la primera hoja
937
938 # Definir las columnas necesarias
939 columnas_requeridas = ["Número Documento", "Zona", "Dirección"]
940 headers = [cell.value.strip() if cell.value else "" for cell in sheet[1]]
941
942 for col in columnas_requeridas:
943 if col not in headers:
944 messages.error(request, f"El archivo no tiene la columna requerida: {col}")
945 return redirect("cargar_direcciones_excel")
946
947 col_idx = {headers[i]: i + 1 for i in range(len(headers))}
948
949 zona_default, _ = Zona.objects.get_or_create(nombre="Los Vilos") # Zona predeterminada
950
951 for row in sheet.iter_rows(min_row=2, values_only=True):
952 numero_documento = limpiar_numero_documento(str(row[col_idx["Número Documento"] - 1]))
953
954 try:
955 cliente = Cliente.objects.get(numero_documento=numero_documento)
956 except Cliente.DoesNotExist:
957 messages.warning(request, f"Cliente con documento {numero_documento} no encontrado, omitiendo.")
958 continue
959
960 zona_nombre = str(row[col_idx["Zona"] - 1]).strip() if row[col_idx["Zona"] - 1] else "Los Vilos"
961 zona, _ = Zona.objects.get_or_create(nombre=zona_nombre)
962
963 DireccionInstalacion.objects.create(
964 cliente=cliente,
965 zona=zona,
966 direccion=row[col_idx["Dirección"] - 1]
967 )
968
969 messages.success(request, "Direcciones cargadas correctamente.")
970 except Exception as e:
971 messages.error(request, f"Error al procesar el archivo: {str(e)}")
972
973 return redirect("cargar_direcciones_excel")
974
975 return render(request, "usuarios/cargar_direcciones.html")
976
977
978
979
980
981
982def informe_ingresos(request):
983 mes = request.GET.get('mes', '')
984 anio = request.GET.get('anio', '')
985
986 # Diccionario para obtener el nombre del mes
987 meses_nombres = {
988 1: "Enero", 2: "Febrero", 3: "Marzo", 4: "Abril",
989 5: "Mayo", 6: "Junio", 7: "Julio", 8: "Agosto",
990 9: "Septiembre", 10: "Octubre", 11: "Noviembre", 12: "Diciembre"
991 }
992
993 # Filtrar pagos y agrupar por mes y año
994 ingresos = Pago.objects.all()
995 if mes:
996 ingresos = ingresos.filter(fecha_pago__month=mes)
997 if anio:
998 ingresos = ingresos.filter(fecha_pago__year=anio)
999
1000 ingresos = ingresos.values("fecha_pago__month", "fecha_pago__year").annotate(
1001 total_ingresos=Sum("monto"),
1002 cantidad_pagos=Count("id")
1003 ).order_by("fecha_pago__year", "fecha_pago__month")
1004
1005 # Convertir el número del mes a su nombre
1006 for ingreso in ingresos:
1007 ingreso["nombre_mes"] = meses_nombres.get(ingreso["fecha_pago__month"], "Desconocido")
1008 ingreso["anio"] = ingreso["fecha_pago__year"]
1009
1010 context = {
1011 "ingresos": ingresos,
1012 "meses": meses_nombres,
1013 "anios": range(2020, 2031),
1014 }
1015 return render(request, "usuarios/informe_ingresos.html", context)
1016
1017
1018def informe_clientes_pagados(request):
1019 # Obtener mes y año desde los parámetros GET (filtros)
1020 mes = request.GET.get('mes', '')
1021 anio = request.GET.get('anio', '')
1022
1023 # Filtrar pagos
1024 pagos = Pago.objects.all()
1025 if mes:
1026 pagos = pagos.filter(fecha_pago__month=mes)
1027 if anio:
1028 pagos = pagos.filter(fecha_pago__year=anio)
1029
1030 # Diccionario para obtener el nombre del mes
1031 meses_nombres = {
1032 1: "Enero", 2: "Febrero", 3: "Marzo", 4: "Abril",
1033 5: "Mayo", 6: "Junio", 7: "Julio", 8: "Agosto",
1034 9: "Septiembre", 10: "Octubre", 11: "Noviembre", 12: "Diciembre"
1035 }
1036
1037 # Agrupar pagos por cliente y obtener la información deseada
1038 pagos = pagos.values(
1039 'cliente__numero_documento',
1040 'cliente__nombre',
1041 'cliente__apellido_paterno',
1042 'cliente__apellido_materno',
1043 'fecha_pago__month',
1044 'fecha_pago__year',
1045 'numero_boleta' # ✅ Se agrega el número de boleta
1046 ).annotate(
1047 monto_total=models.Sum('monto'),
1048 ultimo_pago=models.Max('fecha_pago')
1049 )
1050
1051 # Convertir el número del mes a su nombre
1052 for pago in pagos:
1053 pago["nombre_mes"] = meses_nombres.get(pago["fecha_pago__month"], "Desconocido")
1054
1055 context = {
1056 'pagos': pagos,
1057 'meses': meses_nombres,
1058 'anios': range(2020, 2031),
1059 }
1060 return render(request, 'usuarios/informe_clientes_pagados.html', context)