
import os
import sys

import re
from decimal import Decimal, InvalidOperation, ROUND_HALF_UP, localcontext
from datetime import datetime,time
from django.forms import ValidationError
from django.shortcuts import get_object_or_404
from django.apps import apps
from django.core.exceptions import FieldDoesNotExist
from datetime import date
from django.db import connection
from rest_framework import generics
from rest_framework.response import Response
from rest_framework.views import APIView 
from rest_framework import status
from rest_framework.request import Request
from rest_framework.decorators import api_view
from rest_framework.parsers import MultiPartParser, FormParser
from django.core.mail import send_mail, EmailMessage
from django.conf import settings
from collections import OrderedDict
from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned
from django.db.models.functions import Coalesce, TruncMonth
from django.http import Http404, HttpResponse, HttpResponseServerError, JsonResponse
from django.contrib.auth import authenticate, login, logout
from django.utils.timezone import now
from django.db.models import ProtectedError , Case, When, F , Sum , Q, Prefetch,  Value, DecimalField, Sum, Case, When, OuterRef, ExpressionWrapper
from django.db.models.fields import DecimalField
from app_venin_web_new1.filters import *
from app_venin_web_new1.views.base.base_search_view import BaseSearchView
from venin_web_new1.settings import MEDIA_ROOT , EMAIL_HOST_USER
from app_venin_web_new1.serializers import *
from app_venin_web_new1.models import *
from app_venin_web_new1.services import *
from app_venin_web_new1.utils import *
from dateutil.relativedelta import relativedelta
from datetime import date
from decimal import Decimal

import tempfile
import xlsxwriter

from django.http import HttpResponse


#usuarios
class LoginView(APIView):
    def post(self, request):
        print(request)
        username = request.data.get('username', None)
        password = request.data.get('password', None)
        user = authenticate(username=username, password=password)

        if user:
            login(request, user)
            return Response({"message":"Inicio de sesion exitoso.",
                             "data":UserSerializer(user).data,
                             "status":"success"},status=status.HTTP_200_OK)
        return Response({"message":"usuario no encontrado.",
                         "status":"fail"},
                        status=status.HTTP_404_NOT_FOUND)


class LogoutView(APIView):
    def post(self, request):
        logout(request)

        return Response(status=status.HTTP_200_OK)


class SucursalView(generics.ListCreateAPIView):
    queryset = Sucursal.objects.all()
    serializer_class = SucursalSerializer
    name = "Sucursal List View"
    
    
class SucursalDetail(APIView):
    def get_object(self, pk):
        try:
            return Sucursal.objects.get(pk=pk)
        except Sucursal.DoesNotExist:
            raise Http404
        
    def get(self, request, pk, format=None):
        query = self.get_object(pk)
        serializer = SucursalSerializer(query)
        return Response(serializer.data)
    
class EmpresaView(generics.ListCreateAPIView):
    queryset = Empresa.objects.all()
    serializer_class = EmpresaReadSerializer
    name = "Empresa List View"

class EmpresaDetails(APIView):
    parser_classes = [MultiPartParser, FormParser]

    def get_object(self, pk):
        try:
            return Empresa.objects.get(pk=pk)
        except Empresa.DoesNotExist:
            raise Http404

    def get(self, request, pk, format=None):
        empresa = self.get_object(pk)
        serializer = EmpresaReadSerializer(empresa, context={'request': request})
        return Response(serializer.data)

    def put(self, request, pk, format=None):
        empresa = self.get_object(pk)
        serializer = EmpresaWriteSerializer(
            empresa, 
            data=request.data, 
            partial=True
        )
        
        if serializer.is_valid():
            serializer.save()
            read_serializer = EmpresaReadSerializer(empresa, context={'request': request})
            return Response({
                "status": "success",
                "data": read_serializer.data
            })
        return Response(serializer.errors, status=400)


@api_view(['GET', 'POST'])
def empleado_lista(request):
    if request.method == 'GET':
        empleados = Empleado.objects.all()

        dni = request.query_params.get('dni')
        departamento = request.query_params.get('departamento')
        cargo = request.query_params.get('cargo')
        nombres = request.query_params.get('nombres')

        if dni:
            empleados = empleados.filter(dni__icontains=dni)
        if departamento:
            empleados = empleados.filter(departamento_id=departamento)
        if cargo:
            empleados = empleados.filter(cargo_id=cargo)
        if nombres:
            empleados = empleados.filter(nombres__icontains=nombres) | \
                        empleados.filter(apellido_paterno__icontains=nombres) | \
                        empleados.filter(apellido_materno__icontains=nombres)

        serializer = EmpleadoSerializer(empleados, many=True)
        return Response(serializer.data, status=status.HTTP_200_OK)

    elif request.method == 'POST':
        serializer = EmpleadoSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

@api_view(['GET', 'PUT', 'DELETE'])
def empleado_detalle(request, pk):
    try:
        empleado = Empleado.objects.get(pk=pk)
    except Empleado.DoesNotExist:
        return Response({'error': 'Empleado no encontrado'}, status=status.HTTP_404_NOT_FOUND)

    if request.method == 'GET':
        serializer = EmpleadoSerializer(empleado)
        return Response(serializer.data, status=status.HTTP_200_OK)

    elif request.method == 'PUT':
        serializer = EmpleadoSerializer(empleado, data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_200_OK)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

    elif request.method == 'DELETE':
        empleado.delete()
        return Response({'mensaje': 'Empleado eliminado correctamente'}, status=status.HTTP_204_NO_CONTENT)
    
@api_view(['GET', 'POST'])
def banco_lista(request):
    if request.method == 'GET':
        bancos = Banco.objects.all()
        serializer = BancoSerializer(bancos, many=True)
        return Response(serializer.data, status=status.HTTP_200_OK)

    elif request.method == 'POST':
        serializer = BancoSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

@api_view(['GET', 'PUT', 'DELETE'])
def banco_detalle(request, pk):
    try:
        banco = Banco.objects.get(pk=pk)
    except Banco.DoesNotExist:
        return Response({'error': 'Banco no encontrado'}, status=status.HTTP_404_NOT_FOUND)

    if request.method == 'GET':
        serializer = BancoSerializer(banco)
        return Response(serializer.data, status=status.HTTP_200_OK)

    elif request.method == 'PUT':
        serializer = BancoSerializer(banco, data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_200_OK)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

    elif request.method == 'DELETE':
        banco.delete()
        return Response({'mensaje': 'Banco eliminado correctamente'}, status=status.HTTP_204_NO_CONTENT)

@api_view(['GET', 'POST'])
def departamento_lista(request):
    if request.method == 'GET':
        departamentos = Departamento.objects.all()
        serializer = DepartamentoSerializer(departamentos, many=True)
        return Response(serializer.data, status=status.HTTP_200_OK)

    elif request.method == 'POST':
        serializer = DepartamentoSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

@api_view(['GET', 'PUT', 'DELETE'])
def departamento_detalle(request, pk):
    try:
        departamento = Departamento.objects.get(pk=pk)
    except Departamento.DoesNotExist:
        return Response({'error': 'Departamento no encontrado'}, status=status.HTTP_404_NOT_FOUND)

    if request.method == 'GET':
        serializer = DepartamentoSerializer(departamento)
        return Response(serializer.data, status=status.HTTP_200_OK)

    elif request.method == 'PUT':
        serializer = DepartamentoSerializer(departamento, data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_200_OK)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

    elif request.method == 'DELETE':
        departamento.delete()
        return Response({'mensaje': 'Departamento eliminado correctamente'}, status=status.HTTP_204_NO_CONTENT)

@api_view(['GET', 'POST'])
def cargo_lista(request):
    if request.method == 'GET':
        cargos = Cargo.objects.all()
        serializer = CargoSerializer(cargos, many=True)
        return Response(serializer.data, status=status.HTTP_200_OK)

    elif request.method == 'POST':
        serializer = CargoSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

@api_view(['GET', 'PUT', 'DELETE'])
def cargo_detalle(request, pk):
    try:
        cargo = Cargo.objects.get(pk=pk)
    except Cargo.DoesNotExist:
        return Response({'error': 'Cargo no encontrado'}, status=status.HTTP_404_NOT_FOUND)

    if request.method == 'GET':
        serializer = CargoSerializer(cargo)
        return Response(serializer.data, status=status.HTTP_200_OK)

    elif request.method == 'PUT':
        serializer = CargoSerializer(cargo, data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_200_OK)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

    elif request.method == 'DELETE':
        cargo.delete()
        return Response({'mensaje': 'Cargo eliminado correctamente'}, status=status.HTTP_204_NO_CONTENT)

@api_view(['GET', 'POST'])
def tasasGenerales_lista(request):
    if request.method == 'GET':
        tasasGenerales = TasaGeneral.objects.all()
        serializer = TasaGeneralSerializer(tasasGenerales, many=True)
        return Response(serializer.data, status=status.HTTP_200_OK)

    elif request.method == 'POST':
        serializer = TasaGeneralSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

@api_view(['GET', 'PUT'])
def tasasGenerales_detalle(request, pk):
    try:
        tasaGeneral = TasaGeneral.objects.get(pk=pk)
    except TasaGeneral.DoesNotExist:
        return Response({'error': 'Tasa General no encontrada'}, status=status.HTTP_404_NOT_FOUND)

    if request.method == 'GET':
        serializer = TasaGeneralSerializer(tasaGeneral)
        return Response(serializer.data, status=status.HTTP_200_OK)

    elif request.method == 'PUT':
        serializer = TasaGeneralSerializer(tasaGeneral, data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_200_OK)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

@api_view(['GET', 'POST'])
def regimen_lista(request):
    if request.method == 'GET':
        regimenes = RegimenPensionario.objects.all()
        serializer = RegimenPensionarioSerializer(regimenes, many=True)
        return Response(serializer.data, status=status.HTTP_200_OK)

    elif request.method == 'POST':
        serializer = RegimenPensionarioSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

@api_view(['GET', 'PUT', 'DELETE'])
def regimen_detalle(request, pk):
    try:
        regimen = RegimenPensionario.objects.get(pk=pk)
    except RegimenPensionario.DoesNotExist:
        return Response({'error': 'Regimen Pensionario no encontrado'}, status=status.HTTP_404_NOT_FOUND)

    if request.method == 'GET':
        serializer = RegimenPensionarioSerializer(regimen)
        return Response(serializer.data, status=status.HTTP_200_OK)

    elif request.method == 'PUT':
        serializer = RegimenPensionarioSerializer(regimen, data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_200_OK)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

    elif request.method == 'DELETE':
        regimen.delete()
        return Response({'mensaje': 'Regimen Pensionario eliminado correctamente'}, status=status.HTTP_204_NO_CONTENT)
    
@api_view(['GET', 'POST'])
def tasaMensual_lista(request):
    if request.method == 'GET':
        tasasMensuales = AfpTasasMensuales.objects.all()
        afp_id = request.query_params.get('afp')
        if afp_id:  
            afp_id = afp_id.replace('/', '')
            tasasMensuales = tasasMensuales.filter(afp=afp_id)
        serializer = TasaMensualSerializer(tasasMensuales, many=True)
        return Response(serializer.data, status=status.HTTP_200_OK)

    elif request.method == 'POST':
        serializer = TasaMensualSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

@api_view(['GET', 'PUT', 'DELETE'])
def tasaMensual_detalle(request, pk):
    try:
        tasaMensual = AfpTasasMensuales.objects.get(pk=pk)
    except AfpTasasMensuales.DoesNotExist:
        return Response({'error': 'Tasa Mensual no encontrada'}, status=status.HTTP_404_NOT_FOUND)

    if request.method == 'GET':
        serializer = AfpTasasMensuales(tasaMensual)
        return Response(serializer.data, status=status.HTTP_200_OK)

    elif request.method == 'PUT':
        serializer = AfpTasasMensuales(tasaMensual, data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_200_OK)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

    elif request.method == 'DELETE':
        tasaMensual.delete()
        return Response({'mensaje': 'Tasa Mensual eliminada correctamente'}, status=status.HTTP_204_NO_CONTENT)

@api_view(['POST'])
def sincronizar_tasas_api(request):
    anio = request.data.get('anio')
    mes = request.data.get('mes')
    
    if not anio or not mes:
        return Response({'error': 'Falta enviar el año o el mes.'}, status=status.HTTP_400_BAD_REQUEST)

    
    #Link para consultar la api de la afp 
    url_api = f"http://api.test/consulta/afp/{anio}/{mes}"

    mes_formateado = str(mes).zfill(2)          
    periodo_db = f"{anio}-{mes_formateado}" 

    try:
        respuesta_api = requests.get(url_api, timeout=10)
        datos_api = respuesta_api.json()
    except requests.RequestException:
        return Response({'error': 'No se pudo conectar con la nube de AFPs.'}, status=status.HTTP_503_SERVICE_UNAVAILABLE)

    if datos_api.get('result') != 1 or not datos_api.get('message'):
        return Response({
            'status': 'no_data', 
            'mensaje': f'No se encontraron datos en la nube para el periodo {periodo_db}.'
        }, status=status.HTTP_404_NOT_FOUND)

    tasas_api = datos_api['message']    
    creados = []
    existentes = []

    for item in tasas_api:
        nombre_afp = item.get('afp') 
        
        afp_obj = RegimenPensionario.objects.filter(nombre__icontains=nombre_afp, tipo='AFP').first()
        if not afp_obj:
            continue

        obj, created = AfpTasasMensuales.objects.get_or_create(
            afp=afp_obj,
            periodo=periodo_db,
            defaults={
                'comision_sobre_flujo': item.get('comision_sobre_flujo') or 0.00,
                'comision_anual_sobre_saldo': item.get('comision_anual_sobre_saldo') or 0.00,
                'prima_de_seguro': item.get('prima_de_seguro') or 0.00,
                'aporte_obligatorio': item.get('aporte_obligatorio') or 0.00,
                'remuneracion_maxima_asegurable': item.get('remuneracion_maxima_asegurable') or 0.00
            }
        )
        
        if created:
            creados.append(nombre_afp)
        else:
            existentes.append(nombre_afp)

    if len(creados) == 0 and len(existentes) > 0:
        mensaje = f"Ya están todos los registros existentes para el periodo {periodo_db}."
        codigo = 'completo'
    elif len(creados) > 0 and len(existentes) > 0:
        mensaje = f"Registro parcial completado. Se crearon: {', '.join(creados)}. Ya existían: {', '.join(existentes)}."
        codigo = 'parcial'
    elif len(creados) > 0 and len(existentes) == 0:
        mensaje = f"Se crearon exitosamente todas las tasas para el periodo {periodo_db}."
        codigo = 'exito'
    else:
        mensaje = "No se procesaron registros."
        codigo = 'vacio'

    return Response({
        'status': codigo,
        'mensaje': mensaje,
        'creados': creados,
        'existentes': existentes
    }, status=status.HTTP_200_OK)

@api_view(['GET', 'POST'])
def planilla_lista(request):
    if request.method == 'GET':
        planillas = Planilla.objects.all()

        periodo = request.query_params.get('periodo')
        clase = request.query_params.get('clase')
        descripcion = request.query_params.get('descripcion')

        if periodo:
            planillas = planillas.filter(periodo__icontains=periodo)
        if clase:
            planillas = planillas.filter(clase=clase)
        if descripcion:
            planillas = planillas.filter(descripcion__icontains=descripcion)
            
        serializer = PlanillaSerializer(planillas, many=True)
        return Response(serializer.data, status=status.HTTP_200_OK)

    elif request.method == 'POST':
        serializer = PlanillaSerializer(data=request.data)
        if serializer.is_valid():
            try:
                with transaction.atomic():
                    planilla = serializer.save()
                    
                    tasas = {t.nombre.upper(): t.porcentaje for t in TasaGeneral.objects.all()}

                    planilla.tasa_pensionario = tasas.get('PENSIONARIO') or 0.00
                    planilla.tasa_fcjmms = tasas.get('FCJMMS') or 0.00
                    planilla.tasa_essalud = tasas.get('ESSALUD') or 0.00
                    planilla.tasa_cstr = tasas.get('CSTR') or 0.00
                    planilla.save(update_fields=['tasa_pensionario', 'tasa_fcjmms', 'tasa_essalud', 'tasa_cstr'])

                    empleados = Empleado.objects.all()

                    if planilla.clase == 'GRATIFICACION':
                        detalles_a_crear = _crear_detalles_gratificacion(planilla, empleados, tasas)
                    elif planilla.clase == 'CTS':
                        detalles_a_crear = _crear_detalles_cts(planilla, empleados, tasas)
                    else:
                        detalles_a_crear = []
                        for empleado in empleados:
                            if empleado.fecha_cese and empleado.fecha_cese < planilla.fecha_inicio:
                                continue
                            detalles_a_crear.append(PlanillaDetalle(
                                planilla=planilla,
                                empleado=empleado,
                                cargo=empleado.cargo,
                                departamento=empleado.departamento,
                                afp=empleado.afp,
                                cuspp=empleado.cuspp,
                                fecha_ingreso=empleado.fecha_ingreso,
                                fecha_cese=empleado.fecha_cese,
                                monto_asignacion_familiar=empleado.asignacion_familiar,
                                jornal_diario=empleado.jornal_diario
                            ))

                    PlanillaDetalle.objects.bulk_create(detalles_a_crear)
                    
                return Response(serializer.data, status=status.HTTP_201_CREATED)
            
            except Exception as e:
                return Response({'error': f'Error al generar detalles: {str(e)}'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
                
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

@api_view(['GET', 'PUT', 'DELETE'])
def planilla_detalle(request, pk):
    try:
        planilla = Planilla.objects.get(pk=pk)
    except Planilla.DoesNotExist:
        return Response({'error': 'Planilla no encontrada'}, status=status.HTTP_404_NOT_FOUND)

    if request.method == 'GET':
        serializer = PlanillaSerializer(planilla)
        return Response(serializer.data, status=status.HTTP_200_OK)

    elif request.method == 'PUT':
        serializer = PlanillaSerializer(planilla, data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_200_OK)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

    elif request.method == 'DELETE':
        planilla.delete()
        return Response({'mensaje': 'Planilla eliminada correctamente'}, status=status.HTTP_204_NO_CONTENT)
    
@api_view(['GET', 'POST'])
def planillaDetalle_lista(request):
    if request.method == 'GET':
        planilladetalles = PlanillaDetalle.objects.all()

        planilla_id = request.query_params.get('planilla')
        if planilla_id:
            planilla_id = planilla_id.replace('/', '') 
            planilladetalles = planilladetalles.filter(planilla_id=planilla_id)

        dni = request.query_params.get('dni')
        if dni:
            planilladetalles = planilladetalles.filter(empleado__dni__icontains=dni)

        serializer = PlanillaDetalleSerializer(planilladetalles, many=True)
        return Response(serializer.data, status=status.HTTP_200_OK)

    elif request.method == 'POST':
        serializer = PlanillaDetalleSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

@api_view(['GET', 'PUT', 'DELETE'])
def planillaDetalle_detalle(request, pk):
    try:
        planillaDetalle = PlanillaDetalle.objects.get(pk=pk)
    except PlanillaDetalle.DoesNotExist:
        return Response({'error': 'Detalle de planilla no encontrado'}, status=status.HTTP_404_NOT_FOUND)

    if request.method == 'GET':
        serializer = PlanillaDetalleSerializer(planillaDetalle)
        return Response(serializer.data, status=status.HTTP_200_OK)

    elif request.method == 'PUT':
        serializer = PlanillaDetalleSerializer(planillaDetalle, data=request.data)
        if serializer.is_valid():
            serializer.save()   
            return Response(serializer.data, status=status.HTTP_200_OK)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

    elif request.method == 'DELETE':
        planillaDetalle.delete()
        return Response({'mensaje': 'Detalle de planilla eliminado correctamente'}, status=status.HTTP_204_NO_CONTENT)

UMBRAL_VARIACION_REMUNERACION = Decimal('0.05')

def _contar_meses_y_remuneracion_grat(empleado, inicio_sem, fin_sem):

    detalles_ordinarios = PlanillaDetalle.objects.filter(
        empleado=empleado,
        planilla__clase='ORDINARIA',
        planilla__fecha_inicio__gte=inicio_sem,
        planilla__fecha_fin__lte=fin_sem,
    ).select_related('planilla').order_by('planilla__fecha_inicio')

    meses_validos = 0
    remuneraciones = []

    for detalle in detalles_ordinarios:
        dias_calendario_mes = (detalle.planilla.fecha_fin - detalle.planilla.fecha_inicio).days + 1
        dias_trabajados = detalle.dias_trabajados or Decimal('0')

        mes_completo = dias_trabajados >= 30

        if mes_completo and detalle.total_remuneracion and detalle.total_remuneracion > 0:
            meses_validos += 1
            remuneraciones.append(detalle.total_remuneracion)

    if meses_validos == 0:
        return 0, Decimal('0')

    ultima_remuneracion = remuneraciones[-1]
    promedio = sum(remuneraciones) / Decimal(len(remuneraciones))
    variacion = abs(ultima_remuneracion - promedio) / promedio if promedio else Decimal('0')

    remuneracion_computable = promedio if variacion > UMBRAL_VARIACION_REMUNERACION else ultima_remuneracion

    return meses_validos, remuneracion_computable.quantize(Decimal('0.01'))


def _crear_detalles_gratificacion(planilla, empleados, tasas):
    detalles = []

    if planilla.fecha_inicio.month <= 7:
        inicio_sem = date(planilla.fecha_inicio.year, 1, 1)
        fin_sem = date(planilla.fecha_inicio.year, 6, 30)
    else:
        inicio_sem = date(planilla.fecha_inicio.year, 7, 1)
        fin_sem = date(planilla.fecha_inicio.year, 12, 31)

    tasa_bonif = Decimal(str(tasas.get('BONIF_EXTRAORDINARIA') or 9.00))  

    for empleado in empleados:
        if empleado.fecha_cese and empleado.fecha_cese < inicio_sem:
            continue
        if empleado.fecha_ingreso > fin_sem:
            continue

        meses, remuneracion_computable = _contar_meses_y_remuneracion_grat(empleado, inicio_sem, fin_sem)
        if meses == 0:
            continue

        monto_gratif = (remuneracion_computable * Decimal(meses) / Decimal('6')) \
            .quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)

        monto_bonif = (monto_gratif * tasa_bonif / Decimal('100')) \
            .quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)

        detalles.append(PlanillaDetalle(
            planilla=planilla,
            empleado=empleado,
            cargo=empleado.cargo,
            departamento=empleado.departamento,
            afp=empleado.afp,
            cuspp=empleado.cuspp,
            fecha_ingreso=empleado.fecha_ingreso,
            fecha_cese=empleado.fecha_cese,
            jornal_diario=empleado.jornal_diario,
            monto_asignacion_familiar=empleado.asignacion_familiar or Decimal('0'),

            meses_computables_grat=meses,
            remuneracion_computable_grat=remuneracion_computable,
            gratificaciones=monto_gratif,
            monto_bonif_extraordinaria=monto_bonif,
            total_otros=(monto_gratif + monto_bonif),
            total_a_pagar=(monto_gratif + monto_bonif),

            monto_snp=Decimal('0.00'),
            monto_afp=Decimal('0.00'),
            monto_aporte=Decimal('0.00'),
            monto_prima=Decimal('0.00'),
            monto_comision=Decimal('0.00'),
        ))

    return detalles

DIAS_MES_CTS = Decimal('30')
MESES_ANIO = Decimal('12')
DIAS_ANIO_CTS = Decimal('360')

def _obtener_periodo_cts(fecha_referencia):

    year = fecha_referencia.year
    month = fecha_referencia.month

    if month <= 5:
        # Noviembre-Abril: se liquida el semestre que cerró en abril (Planilla de mayo)
        inicio = date(year - 1, 11, 1)
        fin = date(year, 4, 30)
    else:
        # Mayo-Octubre: se liquida el semestre que cerró en Octubre (Planilla de Noviembre)
        inicio = date(year, 5, 1)
        fin = date(year, 10, 31)

    return inicio, fin


def _calcular_tiempo_y_remuneracion_cts(empleado, inicio_periodo, fin_periodo):
    detalles_ordinarios = PlanillaDetalle.objects.filter(
        empleado=empleado,
        planilla__clase='ORDINARIA',
        planilla__fecha_inicio__gte=inicio_periodo,
        planilla__fecha_fin__lte=fin_periodo,
    ).select_related('planilla').order_by('planilla__fecha_inicio')

    dias_computables_totales = Decimal('0')
    remuneraciones_mensuales = []   
    ultima_remuneracion = Decimal('0')

    for detalle in detalles_ordinarios:
        dias_trabajados = detalle.dias_trabajados or Decimal('0')
        dias_del_mes = min(dias_trabajados, DIAS_MES_CTS)

        if detalle.total_remuneracion and detalle.total_remuneracion > 0:
            dias_computables_totales += dias_del_mes
            ultima_remuneracion = detalle.total_remuneracion
            if dias_trabajados >= DIAS_MES_CTS:
                remuneraciones_mensuales.append(detalle.total_remuneracion)

    if dias_computables_totales == 0:
        return Decimal('0'), Decimal('0'), Decimal('0')

    if remuneraciones_mensuales:
        promedio = sum(remuneraciones_mensuales) / Decimal(len(remuneraciones_mensuales))
        variacion = abs(ultima_remuneracion - promedio) / promedio if promedio else Decimal('0')
        remuneracion_computable = promedio if variacion > UMBRAL_VARIACION_REMUNERACION else ultima_remuneracion
    else:
        remuneracion_computable = ultima_remuneracion

    meses = dias_computables_totales // DIAS_MES_CTS
    dias_restantes = dias_computables_totales - (meses * DIAS_MES_CTS)

    return meses, dias_restantes, remuneracion_computable.quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)

def _obtener_sexto_gratificacion(empleado, inicio_periodo, fin_periodo):
    detalle_grat = PlanillaDetalle.objects.filter(
        empleado=empleado,
        planilla__clase='GRATIFICACION',
        planilla__fecha_inicio__gte=inicio_periodo,
        planilla__fecha_fin__lte=fin_periodo,
    ).select_related('planilla').order_by('-planilla__fecha_fin').first()

    if not detalle_grat or not detalle_grat.total_a_pagar:
        return Decimal('0.00')

    return (detalle_grat.total_a_pagar / Decimal('6')).quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)


def _crear_detalles_cts(planilla, empleados, tasas):
    detalles = []
    inicio_periodo, fin_periodo = _obtener_periodo_cts(planilla.fecha_inicio)

    for empleado in empleados:
        if empleado.fecha_ingreso > fin_periodo:
            continue
        if empleado.fecha_cese and empleado.fecha_cese < inicio_periodo:
            continue

        meses, dias, remuneracion_computable = _calcular_tiempo_y_remuneracion_cts(
            empleado, inicio_periodo, fin_periodo
        )

        if meses == 0 and dias == 0:
            continue

        sexto_gratificacion = _obtener_sexto_gratificacion(empleado, inicio_periodo, fin_periodo)

        base_cts= remuneracion_computable + sexto_gratificacion
        
        monto_cts = (
            ((base_cts / 12 ) * meses) +
            ((base_cts / 365) * dias)
        ).quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)


        detalles.append(PlanillaDetalle(
            planilla=planilla,
            empleado=empleado,
            cargo=empleado.cargo,
            departamento=empleado.departamento,
            afp=empleado.afp,
            cuspp=empleado.cuspp,
            fecha_ingreso=empleado.fecha_ingreso,
            fecha_cese=empleado.fecha_cese,
            jornal_diario=empleado.jornal_diario,
            monto_asignacion_familiar=empleado.asignacion_familiar or Decimal('0'),

            dias_computables_cts=dias,
            meses_computables_cts=meses,
            remuneracion_computable_cts=remuneracion_computable,
            sexto_gratificacion_cts=sexto_gratificacion,

            cts=monto_cts,
            total_otros=monto_cts,
            total_a_pagar=monto_cts,

            monto_snp=Decimal('0.00'),
            monto_afp=Decimal('0.00'),
            monto_aporte=Decimal('0.00'),
            monto_prima=Decimal('0.00'),
            monto_comision=Decimal('0.00'),
        ))

    return detalles

@api_view(['GET', 'POST'])
def cuentaContable_lista(request):
    if request.method == 'GET':
        cuentas = CuentaContable.objects.select_related('concepto').all()
        serializer = CuentaContableSerializer(cuentas, many=True)
        return Response(serializer.data, status=status.HTTP_200_OK)

    elif request.method == 'POST':
        serializer = CuentaContableSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

@api_view(['PUT'])
def cuentaContable_editar(request, pk):
    try:
        cuenta = CuentaContable.objects.get(pk=pk)
    except CuentaContable.DoesNotExist:
        return Response({'error': 'Cuenta Contable no encontrada'}, status=status.HTTP_404_NOT_FOUND)

    serializer = CuentaContableSerializer(cuenta, data=request.data)
    if serializer.is_valid():
        serializer.save()
        return Response(serializer.data, status=status.HTTP_200_OK)
    return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

@api_view(['GET'])
def conceptoContable_lista(request):
    conceptos = ConceptoContable.objects.filter(activo=True).order_by('orden')
    serializer = ConceptoContableSerializer(conceptos, many=True)
    return Response(serializer.data, status=status.HTTP_200_OK)

def construir_asiento(planilla_detalle):
    empleado = planilla_detalle.empleado
    empleado_afp = planilla_detalle.afp 
    empleado_depto = empleado.departamento

    conceptos = ConceptoContable.objects.filter(activo=True)
    filas = []

    for concepto in conceptos:
        monto = getattr(planilla_detalle, concepto.campo_interno, None)
        if monto is None or float(monto) <= 0:
            continue  

        cuenta_asignada = None

        if empleado_afp:
            cuenta_asignada = CuentaContable.objects.filter(concepto=concepto, afp=empleado_afp).first()
        
        if not cuenta_asignada and empleado_depto:
            cuenta_asignada = CuentaContable.objects.filter(concepto=concepto, departamento=empleado_depto).first()
            
        if not cuenta_asignada:
            cuenta_asignada = CuentaContable.objects.filter(concepto=concepto, afp__isnull=True, departamento__isnull=True).first()

        if cuenta_asignada:
            filas.append({
                'codigo_cuenta': cuenta_asignada.codigo_cuenta,
                'descripcion': cuenta_asignada.descripcion,
                'debe': monto if cuenta_asignada.tipo_movimiento == 'DEBE' else 0,
                'haber': monto if cuenta_asignada.tipo_movimiento == 'HABER' else 0,
            })
            
    return filas

@api_view(['GET'])
def planillaDetalle_asiento(request, pk):
    try:
        # 1. Obtener registro 
        detalle = PlanillaDetalle.objects.get(pk=pk)
        planilla = detalle.planilla
        empleado = detalle.empleado
        
        # 2. Armar Cabecera 
        cabecera = {
            "mes_anio": planilla.periodo, 
            "tipo": planilla.clase,
            "persona": f"{empleado.apellido_paterno} {empleado.apellido_materno}, {empleado.nombres}",
            "area": detalle.departamento.nombre if detalle.departamento else "Sin Área",
            "fecha_inicio": planilla.fecha_inicio.strftime('%d/%m/%Y') if planilla.fecha_inicio else ""
        }
        
        # 3. Obtener filas del asiento
        filas = construir_asiento(detalle)
        
        # 4. Calcular totales
        total_debe = sum(float(f['debe']) for f in filas)
        total_haber = sum(float(f['haber']) for f in filas)
        
        # 5. Devolver respuesta
        return Response({
            "cabecera": cabecera,
            "detalle": filas,
            "totales": {
                "debe": round(total_debe, 2),
                "haber": round(total_haber, 2)
            }
        }, status=status.HTTP_200_OK)
        
    except PlanillaDetalle.DoesNotExist:
        return Response({'error': 'Detalle de planilla no encontrado'}, status=status.HTTP_404_NOT_FOUND)
    except Exception as e:
        return Response({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)