diff --git a/cosiap_api/solicitudes/views.py b/cosiap_api/solicitudes/views.py index 7260613126072bcc4e8adb2a58cdb4dc9d511e80..9397958561c87880501d402dd40248aa8e8a4b8f 100644 --- a/cosiap_api/solicitudes/views.py +++ b/cosiap_api/solicitudes/views.py @@ -408,8 +408,7 @@ class CalificarDocumento(BasePermissionAPIView): # Actualizar el status del documento respuesta.status = nuevo_status - if nueva_observacion: - respuesta.observacion = nueva_observacion + respuesta.observacion = nueva_observacion respuesta.save() data['mensaje'] = "Documentos calificados correctamente." diff --git a/cosiap_frontend/src/App.jsx b/cosiap_frontend/src/App.jsx index 21b985b31752f0bdd458322aead37d9e432da292..3f8b9acf51ab0dbfa31d9d84d7ad565b87ff73c9 100644 --- a/cosiap_frontend/src/App.jsx +++ b/cosiap_frontend/src/App.jsx @@ -42,7 +42,7 @@ import CrearFormato from "./components/plantillas/CrearPlantilla"; import useColor from "./components/useColor"; import EditarColorLogo from "./components/design/colorLogo"; import DescargarManual from "./components/plantillas/manual"; - +import DetallesSolicitud from "./components/SolicitudesAdmin/DetallesSolicitud"; function App() { const [viewPageLoader, setViewPageLoader] = useState(false); @@ -141,6 +141,7 @@ function RoutesApp({ setViewPageLoader }) { > } /> } /> + } /> } /> } /> } /> diff --git a/cosiap_frontend/src/api.js b/cosiap_frontend/src/api.js index 4c15e940b5895a86da71da6b098bfb1f2ec2eb08..1bd0844fe888b4628abc84fea970572999cfb1aa 100644 --- a/cosiap_frontend/src/api.js +++ b/cosiap_frontend/src/api.js @@ -120,10 +120,15 @@ const api = { post: (data) => ax.post('api/solicitudes/', data), getById: (id) => ax.get(`api/solicitudes/${id}`), update: (id, data) => ax.put(`api/solicitudes/${id}/`, data), + delete: (id) => ax.delete(`api/solicitudes/${id}`), + calificar_documentos: (id, data) => ax.put(`api/solicitudes/calificar/${id}/`, data), historial: { get: () => ax.get('api/solicitudes/historial'), getById: (id) => ax.get(`api/solicitudes/historial/${id}`), }, + minuta: { + update: (id,data) => ax.put(`api/solicitudes/subir-minuta/${id}/`, data) + }, convenio: { update: (id,data) => ax.put(`api/solicitudes/subir-convenio/${id}/`, data), get: () => ax.get('api/solicitudes/subir-convenio/') @@ -134,7 +139,8 @@ const api = { }, reportes:{ exportar: (params) => ax.get(`api/solicitudes/reportes/exportar`, params) - } + }, + }, dynamicTables: { get: () => ax.get('api/dynamic-tables'), diff --git a/cosiap_frontend/src/components/SolicitudesAdmin/DetallesSolicitud.jsx b/cosiap_frontend/src/components/SolicitudesAdmin/DetallesSolicitud.jsx new file mode 100644 index 0000000000000000000000000000000000000000..fcf954eec6058fce5db0e944334c6da572c44567 --- /dev/null +++ b/cosiap_frontend/src/components/SolicitudesAdmin/DetallesSolicitud.jsx @@ -0,0 +1,830 @@ +import api, { apiUrl } from "@/api"; +import { useParams, useNavigate } from "react-router-dom"; +import { useEffect, useState } from "react"; +import ModalConfirmation from "../common/ui/Modals/ModalConfirmation"; +import Alert from "../common/ui/Alert"; + +export default function DetallesSolicitud( {setViewPageLoader} ) { + const { id_solicitud } = useParams(); + const [solicitud, setSolicitud] = useState(null); + const [minuta, setMinuta] = useState(null); + const [montoAprobado, setMontoAprobado] = useState(null); + const [observacion, setObservacion] = useState(null); + const [status, setStatus] = useState(null); + const [showModal, setShowModal] = useState(false); + + //Estados para alertas + const [showAlertSuccesful, setShowAlertSuccesful] = useState(false); + const [showAlertError, setShowAlertError] = useState(false); + //Variable para el mensaje de la alerta + const [alertMessage, setAlertMessage] = useState(''); + + const navigate = useNavigate(); + + function regimen (value) { + switch (value) { + case "1": + return "Régimen Simplificado de Confianza" + case "2": + return "Sueldos y salarios e ingresos asimilados a salarios" + case "3": + return "Régimen de Actividades Empresariales y Profesionales" + case "4": + return "Régimen de Incorporación Fiscal" + case "5": + return "Enajenación de bienes" + case "6": + return "Régimen de Actividades Empresariales con ingresos a través de Plataformas Tecnológicas" + case "7": + return "Régimen de Arrendamiento" + case "8": + return "Intereses" + case "9": + return "Obtención de premios" + case "10": + return "Dividendos" + case "11": + return "Demás Ingresos" + case "12": + return "Sin obligaciones fiscales" + default: + return value; + } + } + + const obtenerSolicitud = async() => { + try { + setViewPageLoader(true); + + const response = await api.solicitudes.getById(id_solicitud) + + console.log('Extracción de solicitud exitosa', response) + //Actualizamos el monto aprobado + setMontoAprobado(response.data.monto_aprobado) + //Aactualizamos la observacion + setObservacion(response.data.observacion) + setStatus(response.data.status) + setSolicitud(response.data) + } catch (error) { + console.error('Error al obtener solicitud', error); + } finally { + setViewPageLoader(false); + } + } + + useEffect((() => { + obtenerSolicitud() + }), [id_solicitud]) + + const handleStatusDocumentChange = (seccionKey, elementoKey, newStatus) => { + // Actualiza el estado de la solicitud, cambiando el estado del elemento correspondiente + const updatedSolicitud = { ...solicitud }; + updatedSolicitud.formulario.secciones[seccionKey].elementos[elementoKey].respuesta.status = newStatus; + + // Aquí actualizarías el estado o realizarías cualquier acción necesaria + setSolicitud(updatedSolicitud); + + }; + + const handleObservationDocumentChange = (seccionKey, elementoKey, newObservation) => { + // Actualiza el estado de la solicitud, cambiando el estado del elemento correspondiente + const updatedSolicitud = { ...solicitud }; + updatedSolicitud.formulario.secciones[seccionKey].elementos[elementoKey].respuesta.observacion = newObservation; + + // Aquí actualizarías el estado o realizarías cualquier acción necesaria + setSolicitud(updatedSolicitud); + + }; + + const handleMinutaChange = (event) => { + const file = event.target.files[0]; + // Verifica si el archivo es un PDF + if (file && file.type === "application/pdf") { + setMinuta(file); + } else { + setMinuta(null); + setAlertMessage("Solo puedes subir archivos PDF.") + setShowAlertError(true); + event.target.value = ""; // Limpia el input de archivo + } + }; + + const actualizarDatosSolicitud = async() => { + try { + //Creamos un data siempre y agregamos la minuta, observación, status y monto aprobado + // Crear una instancia de FormData + const formData = new FormData(); + formData.append('minuta', minuta); + formData.append('observacion', observacion); + formData.append('status', status); + formData.append('monto_aprobado', montoAprobado); + const data = {'field_updates': formData} + console.log(data) + const response = await api.solicitudes.update(id_solicitud, data, { + headers: { + 'Content-Type': 'multipart/form-data', + }, + }); + + console.log('Actualización de solicitud exitosa', response) + } catch (error) { + console.error(error) + } + }; + + const calificarDocumentos = async() => { + try { + let data = []; + + // Iteramos sobre cada sección del formulario + Object.values(solicitud?.formulario.secciones).forEach((seccion) => { + // Iteramos sobre cada elemento de la sección + Object.values(seccion.elementos).forEach((elemento) => { + // Verificamos si el elemento es de tipo "documento" y tiene una respuesta + if (elemento.tipo === "documento" && elemento.respuesta) { + data.push({ + id_respuesta: elemento.respuesta.id, + nuevo_status: elemento.respuesta.status, // Usamos un string vacío si no hay status + nueva_observacion: elemento.respuesta.observacion // Usamos un string vacío si no hay observación + }); + } + }); + }); + + const response = await api.solicitudes.calificar_documentos(id_solicitud, {'check_documents': data}); + + console.log('Actualización de documentos exitosa', response) + } catch (error) { + console.error(error) + } + }; + + + + const handleSaveChanges = async() => { + setShowModal(false); + try { + setViewPageLoader(true); + + // Actualizamos los datos de la solicitud, excepto la minuta + + //Creamos la data con la observacion, status y monto_aprobado + let datos = { + observacion: observacion, + status: status, + monto_aprobado: montoAprobado + } + + const response = await api.solicitudes.update(id_solicitud, {'field_updates': datos}); + + console.log('Actualización de datos de solicitud exitosa', response) + if (minuta){ + //Agregamos la minuta en un form_data + const formData = new FormData(); + formData.append('minuta', minuta); + // Actualizamos la minuta + const responseMinuta = await api.solicitudes.minuta.update(id_solicitud, formData,{ + headers: { + 'Content-Type': 'multipart/form-data', + }, + }); + console.log('Actualización de minuta de solicitud exitosa', responseMinuta) + + } + // const response = await api.solicitudes.update(id_solicitud, {'field_updates': data}, { + // headers: { + // 'Content-Type': 'multipart/form-data', + // }, + // }); + + console.log('Actualización de solicitud exitosa', response) + + //Calificamos los documentos + let data = []; + + // Iteramos sobre cada sección del formulario + Object.values(solicitud?.formulario.secciones).forEach((seccion) => { + // Iteramos sobre cada elemento de la sección + Object.values(seccion.elementos).forEach((elemento) => { + // Verificamos si el elemento es de tipo "documento" y tiene una respuesta + if (elemento.tipo === "documento" && Object.keys(elemento.respuesta).length !== 0) { + data.push({ + id_respuesta: elemento.respuesta.id, + nuevo_status: elemento.respuesta.status, + nueva_observacion: (elemento.respuesta.observacion.length === 0 ? null : elemento.respuesta.observacion) + }); + } + }); + }); + + console.log(data) + + const responseDoc = await api.solicitudes.calificar_documentos(id_solicitud, {'check_documents': data}); + + console.log('Actualización de documentos exitosa', responseDoc) + + + //Definimos el mensaje de la alerta + setAlertMessage('¡Cambios guardados con exito!'); + setShowAlertSuccesful(true) + + } catch (error) { + console.error('Error al guardar cambios', error); + //Definimos el mensaje de la alerta + setAlertMessage('¡Ha ocurrido un error inesperado! Vuelve a intentarlo'); + setShowAlertError(true) + } finally { + setViewPageLoader(false); + } + } + + return ( + <> + {showAlertSuccesful && ( + + )} + {showAlertError && ( + + )} + + {showModal && ( + + + + + + )} +
+
+
+ {/* Contenedor de la imagen de modalidad */} +
+ Modalidad +
+
+ + + Modalidad: + + + + "{solicitud?.modalidad.nombre}" + + + + {solicitud?.modalidad.descripcion} + + + + $ { + solicitud?.modalidad.monto_maximo.toLocaleString('es-MX', { + minimumFractionDigits: 2, // Número mínimo de decimales + maximumFractionDigits: 2, // Número máximo de decimales + }) + + } MXN + +
+
+ {/* Contenedor con la información del solicitante */} +
+
+

Datos del solicitante

+
+ {/* Contenedor con la información personal del solicitante */} +
+

Informacion personal

+
+
+
+ + Nombre + + + {solicitud?.solicitante.nombre || "(N/A)"} + +
+
+ + Apellido Paterno + + + {solicitud?.solicitante.ap_paterno || "(N/A)"} + +
+
+ + Apellido Materno + + + {solicitud?.solicitante.ap_materno || "(N/A)"} + +
+
+ + Sexo + + + {solicitud?.solicitante.sexo ? (solicitud.solicitante.sexo === 'M' ? 'Masculino' : solicitud.solicitante.sexo === 'F' ? 'Femenino' : 'Otro' ) : "(N/A)"} + +
+
+ + Telefono + + + {solicitud?.solicitante.telefono || "(N/A)"} + +
+
+ + Correo electronico + + + {solicitud?.solicitante.email || "(N/A)"} + +
+
+ {/* Contenedor con la información de direccion del solicitante */} +
+

Dirección

+
+
+
+ + Dirección + + + {solicitud?.solicitante.direccion || "(N/A)"} + +
+
+ + Estado + + + {solicitud?.solicitante.municipio?.estado.nombre || "(N/A)"} + +
+
+ + Municipio + + + {solicitud?.solicitante.municipio?.nombre || "(N/A)"} + +
+
+ + Codigo Postal + + + {solicitud?.solicitante.codigo_postal || "(N/A)"} + +
+
+ {/* Contenedor con la información de identificacion del solicitante */} +
+

Información de identificación

+
+
+
+ + RFC + + + {solicitud?.solicitante.RFC || "(N/A)"} + +
+
+ + CURP + + + {solicitud?.solicitante.curp || "(N/A)"} + +
+
+ + INE + + + + {((solicitud?.solicitante.INE !== null) && (solicitud?.solicitante.INE !== undefined)) ? + ( + Ver archivo + ) + : ("(N/A)") + } + +
+
+ {/* Contenedor con la información de identificacion del solicitante */} +
+

Datos bancarios

+
+
+
+ + Nombre del banco + + + {solicitud?.solicitante.datos_bancarios?.nombre_banco || "(N/A)"} + +
+
+ + Cuenta bancaria + + + {solicitud?.solicitante.datos_bancarios?.cuenta_bancaria || "(N/A)"} + +
+
+ + Clave bancaria + + + {solicitud?.solicitante.datos_bancarios?.clabe_bancaria || "(N/A)"} + +
+
+ + Codigo Postal Fiscal + + + {solicitud?.solicitante.datos_bancarios?.codigo_postal_fiscal || "(N/A)"} + +
+
+ + Regimen + + + {solicitud?.solicitante.datos_bancarios?.regimen ? regimen(solicitud?.solicitante.datos_bancarios?.regimen) : "(N/A)"} + +
+
+ + Estado de cuenta + + + + {((solicitud?.solicitante.datos_bancarios?.doc_estado_cuenta !== null) && (solicitud?.solicitante.datos_bancarios?.doc_estado_cuenta !== undefined)) ? + ( + Ver archivo + ) + : ("(N/A)") + } + +
+
+ + Constancia de situación fiscal + + + + {((solicitud?.solicitante.datos_bancarios?.doc_constancia_sat !== null) && (solicitud?.solicitante.datos_bancarios?.doc_constancia_sat !== undefined)) ? + ( + Ver archivo + ) + : ("(N/A)") + } + +
+
+
+ + {/* Contenedor del formulario*/} +
+ + { + Object.values(solicitud?.formulario?.secciones || {}).map((seccion, key) => ( + <> +
+

{seccion.nombre}

+
+
+ {/* Elementos de la seccion */} + {Object.values(seccion?.elementos || {}).map((elemento, key) =>( + elemento.tipo !== "documento" ? ( +
+ + {elemento.nombre} + + + {elemento.respuesta?.valor || "(N/A)"} + +
+ ) : ( +
+ + {elemento.nombre} + + {elemento.respuesta?.valor ? ( + <> + + + Estatus + +
+ +
+ + Retroalimentación + +
+