106 Commits

Author SHA1 Message Date
stern 51aa3121bc Add deploy.yaml
Despliegue Automatizado Universal / get-context (push) Successful in 1s
Despliegue Automatizado Universal / build (push) Successful in 1m27s
Despliegue Automatizado Universal / deploy (push) Successful in 40s
2026-07-02 03:27:35 -06:00
Patriots01 4b7516f9ec Se agrego la opcion de descargar reportes de caso especial 2026-03-11 22:11:39 +00:00
Patriots01 ef43cf238c Modificaciones 2026-03-07 01:41:29 +00:00
santiago 5fbdf59fb2 Se agrego la opcion de editar casos especiales asi como la opcion de liberar los casos especiales 2026-01-29 12:40:06 -06:00
santiago 3f1412a911 Se puso la opcion de buscar con el enter en casos especiales 2026-01-29 12:37:03 -06:00
santiago c1eead5a3e Modificaciones 2026-01-29 12:20:30 -06:00
santiago 4fdf6429ec Se agrego la vista de editar casos especiales 2026-01-29 11:08:12 -06:00
santiago cb7beef846 Modificaciones 2026-01-28 11:59:16 -06:00
santiago 2146daf642 modificaciones 2026-01-27 10:37:03 -06:00
santiago 1ad4163319 Se modifico la parte del envio de cuestionario y mensajes de alerta 2026-01-27 10:31:36 -06:00
santiago 870d47326a Se actualizo el envio de cuestionario de alumno y se modifico el mensaje 2026-01-26 15:28:15 -06:00
santiago 64d8509363 Modificaciones en el envio de cuestionario alumno 2026-01-26 15:25:54 -06:00
santiago b77726cd36 Modificaciones en el mensaje del envio de cuestionario 2026-01-26 13:41:22 -06:00
santiago 20fa308877 Se modificaron cosas optimizacion del sistema, se agregaron mensajes de alerta y correcion de UX/UI 2026-01-26 10:39:23 -06:00
santiago 40f0f38f61 Se optimizaron varias cosas de la interfas asi como se añadieron mensajes de alerta y cambios en la ui/ux 2026-01-26 09:53:03 -06:00
santiago e2e8793420 Se modificaron cosas de la importacion para un mejor rendimiento asi como los mensajes al hacer peticiones al back, asu como tambien se modificaron los mejos de errores 2026-01-25 13:08:32 -06:00
santiago 4b49987cf5 Modificaciones 2026-01-24 22:12:48 -06:00
santiago 828e56f36f Se quitaron los console.log 2026-01-24 16:32:26 -06:00
santiago 578b3f4c52 Modificaciones 2026-01-23 11:49:25 -06:00
santiago dcb2544bd9 Modificaciones 2026-01-23 11:48:13 -06:00
santiago e0cb71ee45 Modificaciones 2026-01-23 11:38:39 -06:00
santiago a3a9f03025 Modificaciones en el nombre del servicio 2026-01-23 10:42:37 -06:00
santiago 4647014dcd Modificaciones en la forma de registro de un caso especial, y modificaciones en las fechas a es 2026-01-23 10:36:44 -06:00
santiago c50f3ea9f3 Se agregaron las claves del programa de acatlan contigo 2026-01-21 13:54:53 -06:00
santiago fbcde608ed Modificaciones 2026-01-21 11:12:57 -06:00
santiago c291c66fef Modificaciones 2026-01-20 17:34:13 -06:00
santiago 08ac1f064b Modificaciones 2026-01-20 10:49:46 -06:00
santiago 72f27183c4 Modificaciones 2026-01-20 10:46:07 -06:00
santiago 9efbe3851c Modificaciones 2026-01-20 10:42:08 -06:00
santiago cb5889143e Modificaciones 2026-01-19 21:53:31 -06:00
santiago dfb9a0d1d7 Modificaciones 2026-01-17 13:28:51 -06:00
santiago 600741652c Modificaciones 2026-01-16 16:02:24 -06:00
santiago 7bfac7afb1 Modificaciones 2026-01-16 15:50:38 -06:00
santiago 2861751e6a Modificaciones 2026-01-16 15:40:57 -06:00
santiago 7c256e4c2f Modificaciones 2026-01-16 15:23:00 -06:00
santiago 38d6cca656 Modificaciones 2026-01-16 15:21:38 -06:00
santiago 7877d2f8dd Modificaciones 2026-01-16 15:07:59 -06:00
santiago 1c869bd092 Modificaciones 2026-01-16 14:56:55 -06:00
santiago 595fdddd52 Modificaciones 2026-01-16 14:52:19 -06:00
santiago 97f251aa69 Modificaciones 2026-01-16 14:00:32 -06:00
santiago 0147cbf34c Modificaciones 2026-01-16 13:30:16 -06:00
santiago 863783307e Modificaciones 2026-01-16 12:26:49 -06:00
santiago d8276b3c07 Modificaciones 2026-01-16 12:20:50 -06:00
santiago ea4fa4a8b6 Modificaciones 2026-01-16 12:17:58 -06:00
santiago fcfa2d2c3b Modificaciones 2026-01-16 12:14:02 -06:00
santiago 513e8dab8d Merge branch 'develop' 2026-01-16 10:08:45 -06:00
santiago 25eaba5b9c Modificaciones en la fecha 2026-01-16 09:53:15 -06:00
santiago 5bd61a39ad Se cambio el titulo del servicio al de produccion 2026-01-15 14:27:52 -06:00
santiago 28d2ba31aa Modificaciones 2026-01-15 12:00:23 -06:00
santiago f913419122 Se corrigieron errores en los mensajes de alert 2026-01-15 11:46:45 -06:00
santiago f25856a068 Se modifico el mensaje de error en el login 2026-01-15 11:43:35 -06:00
santiago d21ea10b0a Se corrigio un error de sintaxis 2026-01-15 11:41:41 -06:00
santiago 8e6d1c6e80 Se agregaron mensajes de alerta perzonalizados en la vistas de alumno y responsable 2026-01-15 11:39:30 -06:00
santiago ea5b94f48b Modificaciones en los mensajes de alerta 2026-01-15 11:22:20 -06:00
Patriots01 44f9486f45 Modificacines 2026-01-15 00:44:48 +00:00
Patriots01 51d647c751 Modificacinoes 2026-01-14 19:10:56 +00:00
Patriots01 de7d09bad5 Modificaciones 2026-01-14 18:36:49 +00:00
Patriots01 a9ccaf120e Modificaciones 2026-01-14 18:32:51 +00:00
Patriots01 cfe05cb966 Modificaciones 2026-01-14 18:07:53 +00:00
Patriots01 cc47077705 Modificaciones 2026-01-14 17:53:19 +00:00
Patriots01 de3dfe06ca Modificaciones 2026-01-14 01:46:23 +00:00
Patriots01 ef42e30dfa Modificaciones 2026-01-14 01:33:50 +00:00
Patriots01 d297d7474e Modificaciones 2026-01-14 01:20:00 +00:00
Patriots01 39e47e9262 Modificaciones 2026-01-14 01:06:28 +00:00
Patriots01 70143c0fc5 Modificaciones 2026-01-14 00:36:33 +00:00
Patriots01 50aacba98e Modificaciones 2026-01-13 23:46:43 +00:00
Patriots01 b8a279d2b3 Modificaciones 2026-01-13 23:30:02 +00:00
Patriots01 dc431e0df1 Se modifico la vista del usuario para poder habilitar y desabilitar el cuestionario e el infrome 2026-01-13 22:11:08 +00:00
santiago 57c314a085 Modificaciones 2026-01-13 15:45:53 -06:00
santiago 3f9978a43e Modificaiones 2026-01-13 15:21:53 -06:00
Patriots01 922a3bd8de Modificaciones 2026-01-13 21:01:03 +00:00
santiago 7633905fd2 Se modifico el titulo del servicio 2026-01-13 11:30:58 -06:00
santiago 0342f94f5a Modificaciones 2026-01-12 15:12:57 -06:00
santiago 7446782446 Se modifico la vista de responsable para desabilitar en caso de que no cumpla con el status 2026-01-12 10:38:37 -06:00
santiago da2cad15b9 Se soluciono un problema de bucle 2026-01-12 10:23:48 -06:00
santiago c71738333f Se Agregaron cosas de la UI/UX asi como se arregaron algunos errores 2026-01-12 10:03:59 -06:00
santiago 4765105d90 Modificaciones en algunas vistas y en el envio de archivos y un error de dedo 2026-01-08 13:01:54 -06:00
santiago 4dc5256071 Modificaciones en algunas vistas y en el envio de archivos 2026-01-08 12:59:07 -06:00
santiago d3ba08a3cf Se modifico la logia en el envio de archivos falta corregir el endpoint de servicio/update 2026-01-07 19:53:59 -06:00
santiago bd7b34c59c Se modificaron cosas para que funcione con la nueva api, se modifico la funcionalidad asi como el fetching de los datos 2025-12-09 09:19:26 -06:00
santiago feee742080 Merge branch 'pruebas_api_nueva' of https://repositorio.acatlan.unam.mx/smoreno/new_servicio_social_front into develop 2025-12-09 09:12:11 -06:00
santiago 801e1b4205 Se modificaron varias cosas en el fetching de los datos asi como los nombres y estilos de algunas vistas 2025-12-09 09:05:57 -06:00
santiago 3e28503718 Modifificaciones 2025-12-07 09:34:14 -06:00
Patriots01 3de2f08ed2 Se modificaron las rutas de las peticiones 2025-11-27 23:31:06 +00:00
Patriots01 bbb0b0f795 Modificaiones en la fomrma de renderizar la info 2025-11-27 20:29:44 +00:00
Patriots01 6ff9641761 Se modifico el fechind de los datos en la vista de responsable 2025-11-26 22:00:35 +00:00
Patriots01 99adfdebb5 Modificaciones 2025-11-26 21:13:28 +00:00
Patriots01 ac1fd05638 Modificacinoes en los endpoint asi como los metodos 2025-11-25 22:00:20 +00:00
Patriots01 8b27cbd425 Se modificaron cosas de nombres a la hora de renderizar la informacion y se cambiaron los losnmbres de los endpoint asi como tambien las direcciones de algunos endpoint 2025-11-25 21:54:49 +00:00
Patriots01 82b5b1fc50 Modificaciones 2025-11-25 19:54:57 +00:00
Patriots01 e27b1fb643 Modificaciones 2025-11-25 19:03:57 +00:00
Patriots01 da73df3e7d Modificaciones 2025-11-25 17:55:39 +00:00
Patriots01 5a0fe4edde errores corregidos en login 2025-11-24 22:21:57 +00:00
santiago b1bafec1be Modificacines 2025-11-14 21:23:32 +01:00
santiago 763c017ce9 Se agrego la funcionalidad de descargar los reportes en la vista administrador/registro 2025-11-14 17:44:49 +01:00
santiago 36ef050f47 Nuevos estilos y correcion de errores 2025-11-11 20:56:21 +01:00
santiago d58283674d Correcion de error en la visyta de alumno 2025-11-10 22:16:33 +01:00
santiago 41e65d313b Nuevos estilos y se agrego la vista de alumno 2025-11-10 22:13:04 +01:00
santiago ca119f4d25 Modificaciones en la vista 2025-11-08 00:54:06 +01:00
santiago fec91b02c0 Nuevos estilos en las vistas 2025-11-07 00:56:04 +01:00
santiago 5c3556d6c3 Modificaciones en la vista de administrador/caso_especial, se agregaron los status 2025-11-06 18:02:23 +01:00
santiago c35358398e Nuevos estilos en las diferentes vistas 2025-11-05 21:02:02 +01:00
santiago 6500e41fbf Nuevos estilos en las diferentes vistas 2025-11-05 19:51:26 +01:00
santiago b089acca0c Se agregaron nuevos estilos en la vista de responsable/editar 2025-11-05 17:54:14 +01:00
santiago f6aa8505e4 Se modificacion los estilos y las fechas en administrador y casoEspecial 2025-11-04 20:43:40 +01:00
santiago 1cc2637b25 Cambios en el login 2025-10-20 19:17:37 +02:00
72 changed files with 5341 additions and 2032 deletions
+85
View File
@@ -0,0 +1,85 @@
name: Despliegue Automatizado Universal
on:
push:
branches:
- develop
- master
jobs:
get-context:
runs-on: host
outputs:
node_version: ${{ steps.setup.outputs.version }}
target_app: ${{ steps.setup.outputs.app }}
steps:
- name: Resolver Aplicación desde apps.conf
id: setup
run: |
# 1. Obtener el nombre del repositorio actual en Gitea (ej: front-Censo)
REPO_NAME="${{ github.event.repository.name }}"
# 2. Determinar si buscamos un entorno -dev (develop) o producción (master)
if [ "${{ github.ref_name }}" = "develop" ]; then
SUFFIX="-dev"
else
SUFFIX=""
fi
echo "Buscando qué sección de apps.conf tiene REPO=$REPO_NAME y termina en '$SUFFIX'..."
# 3. Buscar en /etc/apps.conf el bloque que contenga REPO=nombre_del_repo
TARGET_APP=$(awk -v repo="REPO=$REPO_NAME" -v suf="$SUFFIX" '
/^\[/ { gsub(/[\[\]]/, "", $0); current_box=$0; next }
$0 == repo {
if ((suf == "-dev" && current_box ~ /-dev$/) || (suf == "" && current_box !~ /-dev$/)) {
print current_box;
exit
}
}
' /etc/apps.conf)
if [ -z "$TARGET_APP" ]; then
echo "ERROR: No se encontró ninguna sección en /etc/apps.conf para el repositorio $REPO_NAME con el sufijo correcto."
exit 1
fi
# 4. Leer la versión de Node del bloque encontrado
VERSION=$(awk -v app="$TARGET_APP" '
$0=="["app"]" { found=1; next }
/^\[/ && found { exit }
found && /^NODE=/ { split($0, a, "="); print a[2]; exit }
' /etc/apps.conf)
MAJOR_VERSION=$(echo "$VERSION" | tr -d '[:space:]' | grep -oE '[0-9]+' | head -n1)
echo "-> ID de Aplicación encontrado: $TARGET_APP"
echo "-> Versión Mayor de Node resuelta: $MAJOR_VERSION"
echo "app=${TARGET_APP}" >> $GITHUB_OUTPUT
echo "version=${MAJOR_VERSION}" >> $GITHUB_OUTPUT
build:
needs: get-context
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Configurar Node.js dinámicamente
uses: actions/setup-node@v4
with:
node-version: ${{ needs.get-context.outputs.node_version }}
- run: npm ci
- run: npm run build
deploy:
needs: [get-context, build]
runs-on: host
steps:
- name: Ejecutar Despliegue en Servidor Físico
run: |
TARGET_APP="${{ needs.get-context.outputs.target_app }}"
BRANCH_NAME="${{ github.ref_name }}"
sudo -u deploy /home/deploy/scripts/deploy-front.sh "$TARGET_APP" "$BRANCH_NAME"
+4
View File
@@ -19,6 +19,10 @@ const eslintConfig = [
"build/**",
"next-env.d.ts",
],
rules: {
// permit using `any` in the codebase, otherwise Next's defaults treat it as an error
'@typescript-eslint/no-explicit-any': 'off',
},
},
];
+925 -503
View File
File diff suppressed because it is too large Load Diff
+5 -3
View File
@@ -11,17 +11,19 @@
"dependencies": {
"axios": "^1.12.2",
"bootstrap": "^5.3.8",
"date-fns": "^4.1.0",
"file-saver": "^2.0.5",
"moment": "^2.30.1",
"next": "15.5.3",
"next": "^16.0.7",
"react": "19.1.0",
"react-bootstrap": "^2.10.10",
"react-datepicker": "^8.7.0",
"react-dom": "19.1.0",
"react-hot-toast": "^2.6.0",
"react-icons": "^5.5.0",
"sass": "^1.92.1",
"sweetalert2": "^11.26.2",
"react-toastify": "^11.0.5",
"sass": "^1.95.0",
"sweetalert2": "^11.26.17",
"sweetalert2-react-content": "^5.1.0",
"validator": "^13.15.15"
},
Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

+7
View File
@@ -0,0 +1,7 @@
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Transformed by: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="SVGRepo_bgCarrier" stroke-width="0"/>

After

Width:  |  Height:  |  Size: 1.1 KiB

+7
View File
@@ -0,0 +1,7 @@
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Transformed by: SVG Repo Mixer Tools -->
<svg fill="#000000" version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="800px" height="800px" viewBox="0 0 519.578 519.578" xml:space="preserve">
<g id="SVGRepo_bgCarrier" stroke-width="0"/>

After

Width:  |  Height:  |  Size: 1.3 KiB

+4
View File
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M21 10L12 5L3 10L6 11.6667M21 10L18 11.6667M21 10V10C21.6129 10.3064 22 10.9328 22 11.618V16.9998M6 11.6667L12 15L18 11.6667M6 11.6667V17.6667L12 21L18 17.6667L18 11.6667" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 491 B

+1
View File
@@ -0,0 +1 @@
correo,nombre,institucion,dependencia,programa,clave
1 correo nombre institucion dependencia programa clave
+23 -5
View File
@@ -2,11 +2,11 @@ import axios from 'axios';
// Crea una instancia base de Axios
export const axiosInstance = axios.create({
baseURL: process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3000/api', // poner la url
baseURL: process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3000', // poner la url
headers: {
'Content-Type': 'application/json',
},
timeout: 10000, // tiempo máximo de espera (10 segundos)
timeout: 20000, // tiempo máximo de espera (20 segundos)
});
// Interceptor para agregar el token automáticamente
@@ -19,9 +19,9 @@ axiosInstance.interceptors.request.use(
config.headers.Authorization = `Bearer ${token}`;
//Quitar estas lienas
config.headers['token'] = token;
config.headers['x-access-token'] = token;
config.headers['token-v2'] = token;
// config.headers['token'] = token;
// config.headers['x-access-token'] = token;
// config.headers['token-v2'] = token;
}
}
@@ -29,6 +29,23 @@ axiosInstance.interceptors.request.use(
},
(error) => Promise.reject(error)
);
// (config) => {
// if (typeof window !== 'undefined') {
// const token = localStorage.getItem('token');
// if (token) {
// // Múltiples formatos según lo que espere el backend
// config.headers.Authorization = `Bearer ${token}`;
// //Quitar estas lienas
// // config.headers['token'] = token;
// // config.headers['x-access-token'] = token;
// // config.headers['token-v2'] = token;
// }
// }
// return config;
// },
// (error) => Promise.reject(error)
// Interceptor para manejar respuestas y errores globales
axiosInstance.interceptors.response.use(
@@ -38,6 +55,7 @@ axiosInstance.interceptors.response.use(
// Token inválido o sesión expirada
if (error.response.status === 401) {
console.warn('Sesión expirada o token inválido.');
console.log('Expiro el token')
if (typeof window !== 'undefined') {
localStorage.clear();
window.location.href = '/'; // redirige automáticamente
@@ -0,0 +1,144 @@
'use client'
import EditarAlumno from "@/components/administrador/editar-caso-especial";
import BotonRegresar from "@/components/boton-regresar";
import { getUserCasoEspecial } from "@/services/userCasoE.services";
import { AxiosError, isAxiosError } from "axios";
import { useParams, useRouter } from "next/navigation";
import { useEffect, useState } from "react";
import Swal from "sweetalert2";
interface Admin {
idTipoUsuario: number;
tipoUsuario?: string;
token: string;
}
interface Props {
idCasoEspecial: number
}
export default function EditarCasoEspecial() {
const [idCasoEspecial, setIdCasoEspecial] = useState<number | null>(null);
const [isLoading, setIsLoading] = useState(false);
const [casoEspecial, setCasoEspecial] = useState<Admin>({
idTipoUsuario: 0,
tipoUsuario: "",
token: ""
});
const [datos, setDatos] = useState({}); // es de casoEspecial
const router = useRouter();
const params = useParams();
const idCasoEspe = Number(params.idCasoEspecial);
// fetch local storage and user data when component mounts
useEffect(() => {
const fetchUser = async () => {
await getLocalInfo();
if (!idCasoEspecial) return;
try {
setIsLoading(true);
const data = await getUserCasoEspecial(idCasoEspecial);
setDatos(data);
} catch (error: unknown) {
const axiosErr = error as AxiosError<{ message: string }>;
const mensaje = axiosErr?.response?.data?.message || 'No se pudo obtener la infromacion del caso especial.';
Swal.fire({
title: 'Error',
icon: 'error',
text: mensaje,
});
}
};
fetchUser();
}, []);
if (isNaN(idCasoEspe)) {
return <p>Servicio inválido</p>;
}
const getLocalInfo = () => {
const storedAdmin: Admin = {
idTipoUsuario: Number(localStorage.getItem('idTipoUsuario')),
tipoUsuario: localStorage.getItem('tipoUsuario') || undefined,
token: localStorage.getItem('token') || '',
}
setCasoEspecial(storedAdmin);
const storageIdCaso = Number(localStorage.getItem('idCasoEspecial'));
setIdCasoEspecial(storageIdCaso);
}
const imprimirError = (err: unknown = {}, title = '¡Hubo un error!', onConfirm = () => {}) => {
let message = 'Ocurrió un error';
if (typeof err === 'string') message = err;
else if (err instanceof Error) message = err.message;
else if (isAxiosError(err) && err.response?.data?.message) message = String(err.response.data.message);
Swal.fire({
title,
text: message,
icon: 'error',
confirmButtonText: 'Entendido',
}).then(() => onConfirm());
try {
const anyErr = err as { err?: string };
if (anyErr.err === 'token error') {
localStorage.clear();
router.replace('/');
}
} catch (_) {}
};
const imprimirMensaje = (message: string, title = '¡Felicidades!', onConfirm = () => {}) => {
Swal.fire({
title,
text: message,
icon: 'success',
confirmButtonText: 'Ok',
}).then(() => onConfirm());
};
const imprimirWarning = (
message: string,
onConfirm = () => {},
title = '¡Espera un minuto!',
onCancel = () => {}
) => {
Swal.fire({
title,
text: message,
icon: 'warning',
showCancelButton: true,
confirmButtonText: 'Confirmar',
cancelButtonText: 'Cancelar',
}).then((result) => {
if (result.isConfirmed) onConfirm();
else onCancel();
});
};
const updateIsLoading = (value: boolean) => setIsLoading(value);
return (
<section className="container px-2 pb-5">
<BotonRegresar />
<EditarAlumno
idCasoEspecial={idCasoEspe}
admin={casoEspecial}
viejo={datos}
updateIsLoading={updateIsLoading}
imprimirError={imprimirError}
imprimirMensaje={imprimirMensaje}
imprimirWarning={imprimirWarning}
/>
</section>
)
}
@@ -3,13 +3,14 @@
import { useState, useEffect } from 'react';
import { useRouter } from 'next/navigation';
import { axiosInstance } from '@/api/config';
import { isAxiosError } from 'axios';
import axios, { AxiosError, isAxiosError } from 'axios';
import BotonRegresar from '@/components/boton-regresar';
import InformacionCasoEspecial from '@/components/administrador/informacion-caso-especial';
import LiberarCasoEspecial from '@/components/administrador/liberar-caso-especial';
import Swal from 'sweetalert2';
import withReactContent from 'sweetalert2-react-content';
import { resolve } from 'path';
@@ -27,9 +28,10 @@ interface Status {
}
interface Alumno {
Usuario?: object;
Carrera?: object;
Status?: Status;
idCasoEspecial: number;
usuario?: object;
carrera?: object;
status?: Status;
}
export default function CasoEspecialPage() {
@@ -38,7 +40,7 @@ export default function CasoEspecialPage() {
const [isLoading, setIsLoading] = useState(false);
const [idCasoEspecial, setIdCasoEspecial] = useState<number | null>(null);
const [admin, setAdmin] = useState<Admin>({});
const [alumno, setAlumno] = useState<Alumno>({ Usuario: {}, Carrera: {}, Status: {} });
const [alumno, setAlumno] = useState<Alumno>({ idCasoEspecial: 0, usuario: {}, carrera: {}, status: {} });
// Funciones de dialogo
const imprimirError = (err: unknown = {}, title = '¡Hubo un error!', onConfirm = () => {}) => {
@@ -58,7 +60,7 @@ export default function CasoEspecialPage() {
const anyErr = err as { err?: string };
if (anyErr.err === 'token error') {
localStorage.clear();
router.push('/');
router.replace('/');
}
} catch (_) {}
};
@@ -78,7 +80,7 @@ export default function CasoEspecialPage() {
title = '¡Espera un minuto!',
onCancel = () => {}
) => {
MySwal.fire({
Swal.fire({
title,
text: message,
icon: 'warning',
@@ -111,14 +113,27 @@ export default function CasoEspecialPage() {
updateIsLoading(true);
try {
const res = await axiosInstance.get(`/caso_especial?idCasoEspecial=${idCasoEspecial}`, { headers: { token: admin.token } });
setAlumno(res.data);
// throw new Error('Error simulado del servidor'); Para simular error del servidor
// await new Promise(resolve => setTimeout(resolve, 3000)); // Simulamos que el back tarda en enviar la infromacion 3 seg
const res = await axiosInstance.get(`/caso-especial/caso_especial/${idCasoEspecial}`);
setAlumno(res.data.data);
console.log("Respuesta del servidor", res.data)
} catch (err: unknown) {
if (isAxiosError(err)) imprimirError(err.response?.data || err.message);
else if (err instanceof Error) imprimirError(err.message);
else imprimirError();
const axiosErr = err as AxiosError<any>;
const mensaje = axiosErr?.response?.data?.message || 'No se cargo la infromacion, por favor intentelo mas tarde.'
Swal.fire({
icon: 'error',
title: 'Error',
text: mensaje,
})
router.replace('/administrador/casos_especiales');
// if (isAxiosError(err)) imprimirError(err.response?.data || err.message);
// else if (err instanceof Error) imprimirError(err.message);
// else imprimirError();
} finally {
updateIsLoading(false);
updateIsLoading(false);
}
};
@@ -129,9 +144,9 @@ export default function CasoEspecialPage() {
useEffect(() => {
if (admin.idTipoUsuario) {
if (admin.idTipoUsuario === 2) router.push('/responsable');
if (admin.idTipoUsuario === 3) router.push('/alumno');
if (admin.idTipoUsuario === 4) router.push('/casoEspecial');
if (admin.idTipoUsuario === 2) router.replace('/responsable');
if (admin.idTipoUsuario === 3) router.replace('/alumno');
if (admin.idTipoUsuario === 4) router.replace('/casoEspecial');
}
if (idCasoEspecial) {
@@ -148,7 +163,7 @@ export default function CasoEspecialPage() {
<InformacionCasoEspecial alumno={alumno} />
{(alumno.Status?.idStatus === 11 || alumno.Status?.idStatus === 12) && (
{(alumno.status?.idStatus === 11 || alumno.status?.idStatus === 12) && (
<LiberarCasoEspecial
idCasoEspecial={idCasoEspecial!}
admin={{ token: admin.token! }}
@@ -160,7 +175,7 @@ export default function CasoEspecialPage() {
)}
{isLoading && (
<div className="fixed inset-0 flex items-center justify-center bg-black bg-opacity-30 z-50">
<div className="fixed inset-0 flex items-center justify-center bg-transparent bg-opacity-30 z-50">
<div className="loader">Cargando...</div>
</div>
)}
@@ -1,12 +1,23 @@
'use client'
import TablaCasosEspeciales from "@/components/administrador/tabla-casos-especiales";
import BotonRegresar from "@/components/boton-regresar";
import { useEffect, useState } from "react";
export default function CasosEspeciales() {
const [admin, setAdmin] = useState<{ idTipoUsuario: number; token?: string } | null>(null);
useEffect(() => {
const idTipoUsuario = Number(localStorage.getItem('idTipoUsuario') ?? 0);
const token = localStorage.getItem('token') ?? undefined;
if (!idTipoUsuario) return; // no logueado
setAdmin({ idTipoUsuario, token });
}, []);
return(
<section className="container px-2 pb-5">
<BotonRegresar />
<TablaCasosEspeciales />
<TablaCasosEspeciales admin={admin ?? undefined}/>
</section>
);
}
+2 -5
View File
@@ -1,18 +1,15 @@
"use client"
'use client'
import Footer from "@/components/layout/footer";
import Header from "@/components/layout/header";
import Logout from "@/components/layout/logout";
import React from "react";
import "@/sass/bootstrap.scss"
console.log("Header", Header);
console.log("Footer", Footer);
export default function AdministradorLayout({
children}: {
children: React.ReactNode;
}) {
return (
return (
<>
<Logout />
<main className="bg-light">{children}</main>
+3
View File
@@ -11,6 +11,9 @@ export default function Administrador() {
const token = localStorage.getItem('token') ?? undefined;
if (!idTipoUsuario) return; // no logueado
setAdmin({ idTipoUsuario, token });
console.log('Datos del administrador', admin)
}, []);
function imprimirError(mensaje: string) {
+32 -7
View File
@@ -1,20 +1,41 @@
'use client'
import Cuestionario from "@/components/administrador/cuestionario";
import GustavoBazPrada from "@/components/administrador/gustavo-baz-prada";
import Reporte from "@/components/administrador/reporte";
import ReporteCasoEspecial from "@/components/administrador/reporte-caso-especial";
import BotonRegresar from "@/components/boton-regresar";
import { type } from "node:os";
import { useEffect, useState, useMemo } from "react";
export default function Registro() {
const years = [2020, 2021, 2022, 2023, 2024, 2025];
const admin = { token: "mi_token_de_prueba" };
//const years = [2020, 2021, 2022, 2023, 2024, 2025];
const [admin, setAdmin] = useState({ token: "" });
const obtenerYears = () => {
const years1 = [];
const fechaActual = new Date();
for( let i = 2020; i <= fechaActual.getFullYear(); i++) {
years1.push(i)
}
return years1
}
//const updateIsLoading = (loading: boolean) => {
// console.log("Loading:", loading);
//};
const years = useMemo(() => obtenerYears(), [])
useEffect(() => {
setAdmin({ token: localStorage.getItem("token") || "" });
console.log("Estos son los datos de years", years)
console.log("Este es el tipo de datos de years", typeof(years))
console.log("Estos son los datos de admin", admin)
console.log("Este es el tipo de datos de admin", typeof(admin))
}, [years]);
return (
<section className="container px-2 pb-5">
<BotonRegresar />
<h2 className="title mb-4">Reportes</h2>
<h2 className="title mb-4 fw-bold">Reportes</h2>
<Cuestionario
years={years}
admin={admin}
@@ -23,7 +44,11 @@ export default function Registro() {
<Reporte admin={admin}/>
<GustavoBazPrada />
<GustavoBazPrada
years={years}
/>
<ReporteCasoEspecial />
</section>
);
}
@@ -49,7 +49,7 @@ export default function Editar() {
try {
updateIsLoading(true)
const res = await axiosInstance.get(`usuario/responsable?idUsuario=${idUsuario}`)
const res = await axiosInstance.get(`usuario/responsable/${idUsuario}`)
//imprimirMensaje(res.data.message)
setData(res.data)
} catch (error) {
@@ -63,33 +63,35 @@ export default function Editar() {
}, []);
return (
<section>
<BotonRegresar />
<section className="container px-2 pb-5">
<div className="pt-4">
<BotonRegresar />
</div>
<EditarResponsable
<EditarResponsable
responsable={data}
imprimirError={imprimirError}
imprimirMensaje={imprimirMensaje}
imprimirWarning={imprimirWarning}
updateIsLoading={updateIsLoading}
/>
<ReasignacionProgramas
responsable={data}
imprimirError={imprimirError}
imprimirMensaje={imprimirMensaje}
imprimirWarning={imprimirWarning}
updateIsLoading={updateIsLoading}
/>
<ReasignacionProgramas
responsable={data}
imprimirError={imprimirError}
imprimirMensaje={imprimirMensaje}
imprimirWarning={imprimirWarning}
updateIsLoading={updateIsLoading}
/>
{/*
<EditarResponsable
token={token ?? undefined}
imprimirError={imprimirError}
imprimirMensaje={imprimirMensaje}
imprimirWarning={imprimirWarning}
updateIsLoading={updateIsLoading}
/>
*/}
{/*
<EditarResponsable
token={token ?? undefined}
imprimirError={imprimirError}
imprimirMensaje={imprimirMensaje}
imprimirWarning={imprimirWarning}
updateIsLoading={updateIsLoading}
/>
*/}
</section>
);
}
@@ -20,7 +20,7 @@ export default function Servicio() {
alert(`Error: ${mensaje}`);
};
// ⚠️ NUEVA función para advertencias (confirmaciones)
// NUEVA función para advertencias (confirmaciones)
const imprimirWarning = (mensaje: string, onConfirm: () => void) => {
if (window.confirm(mensaje)) {
onConfirm();
@@ -46,11 +46,10 @@ export default function Servicio() {
}, []);
return (
<section>
<section className="container">
<BotonRegresar />
{/* <EditarServicio admin={}/> */}
<EditarServicio
admin={admin.token || ''}
admin={{ token: admin.token || '' }}
imprimirError={imprimirError}
imprimirMensaje={imprimirMensajeError}
imprimirWarning={imprimirWarning}
+142 -54
View File
@@ -10,6 +10,9 @@ import Archivo from '@/components/administrador/archivo';
import TituloStatus from '@/components/administrador/titulo-status';
import InformacionServicio from '@/components/administrador/informacion-servicio';
import { Status } from '@/types/responses';
import { resolve } from 'path';
import Swal from 'sweetalert2';
import { Spinner } from 'react-bootstrap';
// Tipos estrictos basados en tus datos
@@ -78,10 +81,10 @@ Programa: { Usuario: {} },
//Interface
interface Datos {
Programa?: Programa;
Carrera?: Carrera;
Usuario?: Usuario;
Status?: Status;
programa?: Programa;
carrera?: Carrera;
usuario?: Usuario;
status?: Status;
creditos?: string;
correo?: string;
fechaRegistro?: string;
@@ -98,10 +101,10 @@ interface Datos {
cartaTermino?: string;
informeGlobal?: string;
idCuestionarioPrograma?: number;
idCuestionarioPrograma2?: number;
idCuestionarioAlumno?: number;
idCuestionarioAlumno2?: number;
cuestionarioPrograma?: {idCuestionarioPrograma: number};
cuestionarioPrograma2?: {idCuestionarioPrograma2: number};
cuestionarioAlumno?: {idCuestionarioAlumno: number};
cuestionarioAlumno2?: {idCuestionarioAlumno2: number};
}
export default function Servicio() {
@@ -130,22 +133,44 @@ export default function Servicio() {
setAdmin({ token });
}, []);
const confirmar = (message: string, onConfirm: () => void) => {
Swal.fire({
title: message,
icon: 'warning',
showCancelButton: true,
confirmButtonText: 'confirmar',
cancelButtonText: 'cancelar',
confirmButtonColor: "#0d6efd",
cancelButtonColor: "#dc3545",
}).then((result) => {
if (result.isConfirmed) {
onConfirm();
}
})
}
// Función para imprimir errores
const imprimirError = (error: unknown) => {
alert(`❌ Error: ${JSON.stringify(error)}`);
Swal.fire('Error', JSON.stringify(error), 'error')
// alert(`❌ Error: ${JSON.stringify(error)}`);
}
const imprimirMensaje = (message: string) => {
alert(`${message}`);
Swal.fire('Exito', message, 'success')
// alert(`✅ ${message}`);
};
const imprimirWarning = (message: string, onConfirm: () => void) => {
if (confirm(`⚠️ ${message}\n¿Deseas continuar?`)) {
onConfirm();
}
confirmar(message, onConfirm)
// if (confirm(`
// ${Swal.fire('Aviso', message, 'warning')}
// `)) {
// onConfirm();
// }
};
// Función para actualizar el estado de carga
// Función para acualizar el estado de carga
const updateIsLoading = (value: boolean) => {
setIsLoading(value);
};
@@ -168,42 +193,71 @@ export default function Servicio() {
const tipoUsuario = localStorage.getItem('tipoUsuario') ?? '';
const token = localStorage.getItem('token');
const idServicioLS = Number(localStorage.getItem('idServicio'));
setIdServicio(idServicioLS);
if (!idUsuario || !idTipoUsuario || !token) return null;
const adminInfo: AdminInfo = {
idUsuario,
idTipoUsuario,
tipoUsuario,
token: {
headers: {
return {
idUsuario,
idTipoUsuario,
tipoUsuario,
token: {
headers: {
token,
},
},
};
},
};
//setAdmin(adminInfo);
const idServisio = setIdServicio(Number(localStorage.getItem('idServicio')));
console.log('ID Servicio obtenido:', idServisio);
return adminInfo;
// const adminInfo: AdminInfo = {
// idUsuario,
// idTipoUsuario,
// tipoUsuario,
// token: {
// headers: {
// token,
// },
// },
// };
// //setAdmin(adminInfo);
// const idServisio = setIdServicio(Number(localStorage.getItem('idServicio')));
// console.log('ID Servicio obtenido:', idServisio);
// return adminInfo;
};
const obtenerRegistro = async (adminInfo: AdminInfo, idServicioVal: number) => {
try {
updateIsLoading(true);
const res = await axiosInstance.get(`/servicio/admin?idServicio=${idServicioVal}`); //, adminInfo.token
// await new Promise(resolve => setTimeout(resolve, 3000));
// throw new Error('Api caida')
const res = await axiosInstance.get(`/servicio/admin/${idServicioVal}`); //, adminInfo.token
console.log('Respuesta del servicio:', res.data);
setDatos(res.data);
console.log('Programa obtenido:', datos.Status);
console.log('Programa obtenido:', datos.status);
} catch (error) {
//imprimirError(error.response?.data || { message: 'Error al obtener el registro' });
Swal.fire('Aviso', 'Error al cargar los datos. Intente nuevamente más tarde.', 'error')
console.log('Error al obtener el registro:', error);
router.replace('/administrador')
} finally {
updateIsLoading(false);
}
};
const tieneCuestionarioPrograma =
datos.cuestionarioPrograma?.idCuestionarioPrograma != null ||
datos.cuestionarioPrograma2?.idCuestionarioPrograma2 != null;
const tieneCuestionarioAlumno =
datos.cuestionarioAlumno?.idCuestionarioAlumno != null ||
datos.cuestionarioAlumno2?.idCuestionarioAlumno2 != null;
// === Ciclo de vida (similar a created() en Vue) ===
useEffect(() => {
console.log("Datos que le llegan al useEffect de servicio page:", datos);
const adminInfo = getLocalhostInfo();
if (!adminInfo) {
@@ -239,16 +293,29 @@ export default function Servicio() {
<section className="container px-2 pb-6">
<BotonRegresar />
<h3 className="title">{datos.Status?.status}</h3>
<InformacionServicio
datos={datos}
admin={admin}
imprimirError={imprimirError}
updateIsLoading={updateIsLoading}
/>
<h3 className="container fw-bold title">{datos.status?.status}</h3>
<TituloStatus status={datos.Status}/>
{isLoading ? (
<Spinner />
) : datos ? (
<InformacionServicio
datos={datos}
admin={admin}
imprimirError={imprimirError}
updateIsLoading={updateIsLoading}
/>
) : null}
{/* {!isLoading && datos && (
<InformacionServicio
datos={datos}
admin={admin}
imprimirError={imprimirError}
updateIsLoading={updateIsLoading}
/>
)} */}
<TituloStatus status={datos.status}/>
{/* Para mostrar si tiene la carta y poder ver los diferentes archivos */}
{datos.cartaAceptacion && (
@@ -265,7 +332,7 @@ export default function Servicio() {
{datos.cartaTermino && (
<Archivo
title={datos.cartaTermino ? "carta de término" : ""}
title={datos.cartaTermino ? "carta de termino" : ""}
datos={datos}
admin={admin}
imprimirMensaje={imprimirMensaje}
@@ -287,31 +354,48 @@ export default function Servicio() {
/>
)}
<div>
{datos.idCuestionarioPrograma || datos.idCuestionarioPrograma2 ? (
<div className="container">
{tieneCuestionarioPrograma && (
<p className="my-4">
<strong>Cuenta con cuestionario de programa resuelto.</strong>
</p>
)}
{tieneCuestionarioAlumno && (
<p className="my-4">
<strong>Cuenta con cuestionario de alumno resuelto.</strong>
</p>
)}
</div>
{/* <div className='container'>
{datos.cuestionarioPrograma2?.idCuestionarioPrograma2 != null || datos.cuestionarioPrograma?.idCuestionarioPrograma != null ? (
<p className="my-4">
<strong>Cuenta con cuestionario de programa resuelto.</strong>
</p>
) : null}
{datos.idCuestionarioAlumno || datos.idCuestionarioAlumno2 ? (
{datos.cuestionarioAlumno2?.idCuestionarioAlumno2 != null || datos.cuestionarioAlumno?.idCuestionarioAlumno != null ?(
<p className="my-4">
<strong>Cuenta con cuestionario de alumno resuelto.</strong>
</p>
) : null}
</div>
</div> */}
<ConfirmarServicio
idServicio={idServicio ?? 0}
admin={admin}
datos={datos}
imprimirMensaje={imprimirMensaje}
imprimirWarning={imprimirWarning}
imprimirError={imprimirError}
updateIsLoading={updateIsLoading}
/>
<CancelarServicio
{idServicio && datos.status && (
<ConfirmarServicio
idServicio={idServicio ?? 0}
admin={admin}
datos={datos}
imprimirMensaje={imprimirMensaje}
imprimirWarning={imprimirWarning}
imprimirError={imprimirError}
updateIsLoading={updateIsLoading}
/>
)}
{datos.status?.idStatus != 5 && datos.status?.idStatus != 6 && (
<CancelarServicio
idServicio={idServicio ?? 0}
admin={admin}
datos={datos}
@@ -320,6 +404,8 @@ export default function Servicio() {
imprimirError={imprimirError}
updateIsLoading={updateIsLoading}
/>
)}
{/*
@@ -373,7 +459,9 @@ export default function Servicio() {
*/}
<BotonRegresar />
<div className='container mt-4'>
<BotonRegresar />
</div>
{isLoading && (
<div className="loading-overlay">
@@ -0,0 +1,46 @@
'use client';
import React from 'react';
import FormularioCuestionario from '@/components/alumno/cuestionario2/full-cuestionario-newbad';
import BotonRegresar from '@/components/boton-regresar';
import FullCuestionarioNewBad2 from '@/components/alumno/cuestionario2/full-cuestionario-newbad2';
import FullCuestionario from '@/components/alumno/cuestionario2/full-cuestionario';
import { useParams } from 'next/navigation';
interface Props {
idServicio: number;
}
export default function Page() {
const params = useParams();
const idServicio = Number(params.idServicio);
if (isNaN(idServicio)) {
return <p>Servicio inválido</p>;
}
return (
<div className='container bg-light'>
<div className='mt-4'>
<BotonRegresar />
</div>
<div className='my-1'>
<h1 className='is-size-2'>2025 evaluación del universitario(a) sobre su servicio social</h1>
<p>
Te invitamos a compartir tu experiencia respecto del programa de servicio social en el que
participaste, a efecto de mejorar la oferta de programas disponibles para los alumnos que
desean liberar este requisito. Te recordamos que tus respuestas son confidenciales.
Los datos están sujetos al aviso de privacidad integral que se puede consultar en el sitio
web: <a
href="https://www.acatlan.unam.mx/normatividad"
className="column text-decoration-none text-morado"
target="_blank"> www.acatlan.unam.mx/normatividad </a>
</p>
</div>
<FullCuestionario idServicio={idServicio}/>
</div>
);
}
-16
View File
@@ -1,16 +0,0 @@
'use client';
import React from 'react';
import FormularioCuestionario from '@/components/alumno/cuestionario2/full-cuestionario-newbad';
import BotonRegresar from '@/components/boton-regresar';
import FullCuestionarioNewBad2 from '@/components/alumno/cuestionario2/full-cuestionario-newbad2';
import FullCuestionario from '@/components/alumno/cuestionario2/full-cuestionario';
export default function Page() {
return (
<div className='bg-light'>
<BotonRegresar />
<FullCuestionario />
</div>
);
}
+133 -27
View File
@@ -1,27 +1,118 @@
"use client"
import { axiosInstance } from "@/api/config";
import BarraProgreso from "@/components/alumno/barra-progreso";
import CompletarDatosPersonales from "@/components/alumno/completar-datos-personales";
import NavCues from "@/components/alumno/cuestionario/nav-cues";
import InformacinoServicio from "@/components/alumno/informacion-servicio";
import MensajeAlumno from "@/components/alumno/mensajes-alumno";
import PreTermino from "@/components/alumno/pre-termino";
import { useEffect, useState } from "react";
interface InfoAlumnoExtendido extends InfoAlumno {
Usuario: { usuario: string, nombre: string},
}
interface Usuario {
usuario: string,
nombre: string,
}
interface InfoAlumno{
idServicio: number,
creditos: string,
correo: string,
telefono: string,
direccion: string,
fechaInicio: string,
fechaFin: string,
fechaLiberacion: string,
informeGlobal: string, // Falta ajustar el tipo de datos
programaInterno: string,
profesor: string,
//createdAt: Date,
idCuestionarioAlumno: number,
idCuestionarioAlumno2: number,
carrera: Carrera,
status: Status,
programa: Programa,
}
interface Carrera {
idCarrera: number,
carrera: string,
}
interface Status {
idStatus: number,
status: string,
}
interface Programa {
idPrograma: number,
institucion: string,
dependencia: string,
programa: string,
clavePrograma: string,
}
export default function Home() {
const [data, setData] = useState<InfoAlumnoExtendido>();
// const [servicio, setServicio] = useState<InfoAlumno>();
const handleInfo = async () => {
const idUsuario = localStorage.getItem('idUsuario');
const usuario = localStorage.getItem('usuario');
const nombre = localStorage.getItem('nombre');
const tokenAlumno = localStorage.getItem('token')
console.log('entro para hacer el fetch')
console.log('Este es el id del usuario', idUsuario)
try {
const inf = await axiosInstance.get(`/servicio/alumno?idUsuario=${idUsuario}`)
const Usuario = {
usuario, nombre,
}
console.log("Esta es la inforamcion que trae el back", inf);
const combinado: InfoAlumnoExtendido = { ...inf.data, Usuario };
console.log('Esta es la info combinada', combinado)
// setServicio(inf.data.servicio);
// console.log("Este es el servicio", servicio);
setData(combinado);
console.log("Este es el status del alumno", data?.status.idStatus)
console.log("Este es la infromacion combinada", data);
} catch (error) {
console.log("Error al hacer la peticion ", error)
}
}
useEffect(() => {
if (data) {
console.log("Este es el data en el useEffect", data);
console.log("Este es el status en el useEffect", data.status.idStatus);
}
handleInfo();
}, []);
return (
<div>
<BarraProgreso idStatus={5}/>
<CompletarDatosPersonales
idServicio={123}
alumno={{ token: { headers: { Authorization: "Bearer ..." } }, tokenArchivo: { headers: { Authorization: "Bearer ..." } } }}
imprimirMensaje={(msg) => console.log(msg)}
imprimirWarning={(msg, callback) => { if (confirm(msg)) callback(); }}
imprimirError={(err) => console.error(err)}
obtenerServicio={() => console.log("obtener servicio")}
updateIsLoading={(loading) => console.log("loading", loading)}
/>
{ data?.status.idStatus && (
<BarraProgreso idStatus={data?.status.idStatus}/>
)}
<InformacinoServicio
servicio={{
<div className="my-5">
<MensajeAlumno status={{ idStatus: data?.status.idStatus ?? 6 }} />
</div>
{/* servicio={{
Programa: {
institucion: "UNAM",
dependencia: "Académicos",
@@ -35,25 +126,40 @@ export default function Home() {
fechaInicio: new Date().toISOString(),
fechaFin: new Date().toISOString(),
createdAt: new Date().toISOString(),
}}
/>
}} */}
{ data && (
<InformacinoServicio
servicio={data}
/>
)}
<MensajeAlumno Status={{ idStatus: 2 }} />
{ data?.status.idStatus === 2 && (
<CompletarDatosPersonales
idServicio={data.idServicio}
alumno={{ token: { headers: { Authorization: "Bearer ..." } }, tokenArchivo: { headers: { Authorization: "Bearer ..." } } }}
imprimirMensaje={(msg) => console.log(msg)}
imprimirWarning={(msg, callback) => { if (confirm(msg)) callback(); }}
imprimirError={(err) => console.error(err)}
obtenerServicio={() => console.log("obtener servicio")}
updateIsLoading={(loading) => console.log("loading", loading)}
/>
)}
<PreTermino
alumno={{ tokenArchivo: "token123" }}
servicio={{ idServicio: 123, informeGlobal: undefined }}
imprimirMensaje={(msg) => console.log(msg)}
imprimirWarning={(msg, callback) => {
if (confirm(msg)) callback();
}}
imprimirError={(err) => console.error(err)}
obtenerServicio={() => console.log("obtener servicio")}
updateIsLoading={(loading) => console.log("loading", loading)}
/>
{ data?.status.idStatus !== undefined && data?.status.idStatus >= 4 && data?.status.idStatus !== 8 && (
<PreTermino
alumno={{tokenAlumno: localStorage.getItem('token') || ""}}
servicio={data}
imprimirMensaje={(msg) => console.log(msg)}
imprimirWarning={(msg, callback) => {
if (confirm(msg)) callback();
}}
imprimirError={(err) => console.error(err)}
obtenerServicio={() => console.log("obtener servicio")}
updateIsLoading={(loading) => console.log("loading", loading)}
/>
)}
</div>
);
}
+11
View File
@@ -0,0 +1,11 @@
'use client'
import Logout from "@/components/layout/logout";
export default function Layout({ children }: { children: React.ReactNode}) {
return (
<>
<Logout />
{children}
</>
)
}
+62
View File
@@ -0,0 +1,62 @@
'use client';
import BotonRegresar from "@/components/boton-regresar";
import CasoEspecialForm from "@/components/casoEspecial/caso-especial-form";
import { useState } from "react";
import Swal from "sweetalert2";
export default function Nuevo() {
const [isLoading, setIsLoading] = useState<boolean>(false);
// const token = localStorage.getItem('token');
// console.log("Este es el token en nuevo caso especial:", token);
const imprimirError = (error: unknown) => {
alert(`Error: ${JSON.stringify(error)}`)
}
const imprimirMensaje = (message: string) => {
alert(`${message}`);
}
const imprimirWarning = (message: string, onConfirm: () => void, title: string) => {
Swal.fire({
title,
text: message,
icon: "warning",
showCancelButton: true,
confirmButtonText: 'confirmar',
cancelButtonText: 'cancelar',
confirmButtonColor: "#0d6efd",
cancelButtonColor: "#dc3545",
}).then((result) => {
if (result.isConfirmed) {
onConfirm();
}
})
// if (confirm(`${message}\n¿Desea continuar?`)) {
// onConfirm();
// }
}
const updateIsLoading = (value: boolean) => {
setIsLoading(value);
}
return (
<div className="container">
<BotonRegresar />
<h2 className="fw-bold">Agregar un Servicio Social</h2>
<CasoEspecialForm
imprimirError={imprimirError}
imprimirMensaje={imprimirMensaje}
imprimirWarning={imprimirWarning}
updateIsLoading={updateIsLoading}
/>
{/* <CasoEspecialForm /> */}
</div>
)
}
+2 -2
View File
@@ -49,8 +49,8 @@ export default function Page() {
return (
<section className="container px-2 pb-6">
<div className="pb-5 pt-6">
<button className="btn btn-info" onClick={() => router.push('/casoEspecial/nuevo')}>Nuevo Caso Especial</button>
<div className="pb-4 pt-6 mt-5 mb-4 border-b border-gray-200">
<button className="rounded-2" onClick={() => router.push('/casoEspecial/nuevo')}>Nuevo Caso Especial</button>
</div>
{responsable.idTipoUsuario !== undefined && responsable.token ? (
+115 -4
View File
@@ -1,3 +1,4 @@
@import "react-datepicker/dist/react-datepicker.css";
:root {
--background: #ffffff;
--foreground: #171717;
@@ -10,9 +11,6 @@
}
}
@import "react-datepicker/dist/react-datepicker.css";
html,
body {
max-width: 100vw;
@@ -20,7 +18,7 @@ body {
}
body {
color: var(--foreground);
/* Do not override text color globally so Bootstrap utilities and components keep their intended colors */
background: var(--background);
font-family: Arial, Helvetica, sans-serif;
-webkit-font-smoothing: antialiased;
@@ -60,6 +58,17 @@ button {
}
}
/* Ensure fw-bold utility is available and wins if other rules override it */
.fw-bold,
h1.fw-bold, h2.fw-bold, h3.fw-bold, h4.fw-bold, h5.fw-bold, h6.fw-bold {
font-weight: 700 !important;
}
/* Ensure form labels are easily readable and bold like before */
.form-label {
font-weight: 700 !important;
}
table {
width: 100%;
@@ -138,3 +147,105 @@ table {
}
}
}
/* Para los estilos en la vista de login */
.password-wrapper {
display: flex;
align-items: center;
padding: 0.375rem 0.75rem;
border: 1px solid #ced4da;
border-radius: 0.375rem;
background-color: #fff;
}
.password-wrapper:focus-within {
border-color: #0d6efd; /* Bootstrap primary */
box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
}
.password-input {
padding: 0.375rem 0.75rem;
border: none;
outline: none;
width: 100%;
background: transparent;
color: black;
}
/* Estilos para los calendarios de las fechas */
/* CONTENEDOR GENERAL */
.mi-calendario {
font-family: inherit;
border-radius: 8px;
border: 1px solid #0d6efd;
}
/* HEADER (mes / año) */
.mi-calendario .react-datepicker__header {
background-color: #838383;
border-bottom: none;
}
/* TEXTO DEL HEADER */
.mi-calendario .react-datepicker__current-month,
.mi-calendario .react-datepicker__day-name {
color: #ffffff;
font-weight: 500;
}
/* SELECTS DE MES Y AÑO */
.mi-calendario .react-datepicker__month-select,
.mi-calendario .react-datepicker__year-select {
background-color: transparent;
color: #ffffff;
border: 1px solid #ffffff;
border-radius: 4px;
padding: 2px 6px;
}
/* OPCIONES DEL SELECT */
.mi-calendario .react-datepicker__month-select option,
.mi-calendario .react-datepicker__year-select option {
background-color: #ffffff;
color: #212529;
}
/* DÍAS */
.mi-calendario .react-datepicker__day {
color: #212529;
}
/* DÍA SELECCIONADO */
.mi-calendario .react-datepicker__day--selected,
.mi-calendario .react-datepicker__day--keyboard-selected {
background-color: #0d6efd;
color: #ffffff;
}
/* HOVER DE DÍAS */
.mi-calendario .react-datepicker__day:hover {
background-color: #cfe2ff;
}
/* DÍAS DESHABILITADOS */
.mi-calendario .react-datepicker__day--disabled {
color: #adb5bd;
}
.mi-calendario .react-datepicker__year-select::-webkit-scrollbar {
width: 8px;
}
.mi-calendario .react-datepicker__year-select::-webkit-scrollbar-track {
background: #e9ecef;
}
.mi-calendario .react-datepicker__year-select::-webkit-scrollbar-thumb {
background-color: #0d6efd;
border-radius: 10px;
border: 2px solid #e9ecef;
}
.mi-calendario .react-datepicker__year-select::-webkit-scrollbar-thumb:hover {
background-color: #0b5ed7;
}
+11 -1
View File
@@ -3,18 +3,28 @@ import Header from "@/components/layout/header";
import "./globals.css";
import Footer from "@/components/layout/footer";
import "@/sass/bootstrap.scss"
import { Metadata } from 'next';
export const metadata: Metadata = {
title: 'IRIS',
description: 'Este es un servicio para hacer el servicio social de la FES Acatlan',
icons: {
icon: '/favicon.ico'
}
}
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
}>) {
return (
<html lang="en">
<body className="d-flex flex-column min-vh-100">
<Header />
<main className="flex-grow-1 bg-light">
{children}
{/* <ToastContainer position="top-right" autoClose={3000} /> */}
</main>
<Footer />
</body>
+55 -11
View File
@@ -1,18 +1,36 @@
'use client'
import { axiosInstance } from "@/api/config";
import Image from "next/image";
import { useRouter } from "next/navigation";
import React from "react";
import React, { useState } from "react";
import Swal from "sweetalert2";
export default function Home() {
const [showPassword, setShowPassword] = useState(false);
const router = useRouter();
const [error, setError] = React.useState('');
const [loadingData, setLoadingData] = React.useState({
usuario: '',
password: '',
});
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const { name, value } = e.target;
setLoadingData((prevData) => ({ ...prevData, [name]: value}));
if (error) setError('');
}
const handleOnSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
const formData = new FormData(e.currentTarget);
const object = Object.fromEntries(formData);
// const formData = new FormData(e.currentTarget);
// const object = Object.fromEntries(formData);
try {
const res = await axiosInstance.post('/usuario/login', object);
const res = await axiosInstance.post('/auth/login', {
usuario: loadingData.usuario,
password: loadingData.password,
});
// Extraer datos del backend
const token = res?.data?.token ?? '';
@@ -31,6 +49,7 @@ export default function Home() {
localStorage.setItem('nombre', String(nombre));
localStorage.setItem('idTipoUsuario', String(idTipoUsuario));
// Validar y redirigir según el idTipoUsuario
if (token && idUsuario && idTipoUsuario) {
switch (idTipoUsuario) {
@@ -48,19 +67,26 @@ export default function Home() {
break;
default:
console.warn(`Tipo de usuario desconocido: ${idTipoUsuario}`);
localStorage.clear();
//localStorage.clear();
break;
}
} else {
localStorage.clear();
//localStorage.clear();
console.error('Error: datos de usuario incompletos');
}
} catch (error) {
Swal.fire("Error", "Por favor verifique que el usuario o la contraseña sean correctos.", "error");
// toast.error("Usuario o contraseña estan mal")
//alert("Usuario o password mal")
console.error('Error en el inicio de sesión:', error);
localStorage.clear();
//localStorage.clear();
}
};
const togglePassword = () => {
setShowPassword(prev => !prev)
}
return (
<div className="d-flex justify-content-center align-items-center bg-light" style={{ minHeight: 'calc(100vh - 200px)' }}>
<form className="p-4 shadow rounded bg-white w-100" style={{ maxWidth: '400px' }} onSubmit={handleOnSubmit}>
@@ -68,12 +94,30 @@ export default function Home() {
<div className="mb-3">
<label htmlFor="usuario" className="form-label">Usuario</label>
<input type="text" name="usuario" id="usuario" className="form-control" required />
<input type="text" name="usuario" id="usuario" className="form-control" onChange={handleChange} required />
</div>
<div className="mb-4">
<label htmlFor="password" className="form-label">Contraseña</label>
<input type="password" name="password" id="password" className="form-control" required />
<label htmlFor="password" className="form-label">Contraseña</label>
<div className="password-wrapper d-flex align-items-center mb-4 p-0">
<input id="password" type={showPassword ? "text" : "password"} className="password-input " name="password" onChange={handleChange} required />
<button
type="button"
onClick={togglePassword}
className="btn btn-light p-1 bg-transparent border-0"
aria-label="Mostrar u ocultar contraseña"
>
<Image
src={
showPassword
? "/image/eyeopen.svg"
: "/image/eyeclose.svg"
}
width={24}
height={24}
alt="Mostrar u ocultar contraseña"
/>
</button>
</div>
<div className="d-grid">
@@ -98,15 +98,15 @@ export default function Page() {
{idServicio !== null && (
<UploadArchivo
path="carta_aceptacion"
tipoCarta="aceptación"
variableName="cartaAceptacion"
idServicio={idServicio}
responsable={{ tokenArchivo: responsable.tokenArchivo }}
imprimirMensaje={imprimirMensaje}
imprimirWarning={imprimirWarning}
imprimirError={imprimirError}
updateIsLoading={updateIsLoading}
path="carta_aceptacion"
tipoCarta="aceptación"
variableName="cartaAceptacion"
idServicio={idServicio}
responsable={{ tokenArchivo: responsable.tokenArchivo }}
imprimirMensaje={imprimirMensaje}
imprimirWarning={imprimirWarning}
imprimirError={imprimirError}
updateIsLoading={updateIsLoading}
/>
)}
+32 -10
View File
@@ -8,7 +8,7 @@ import { axiosInstance } from "@/api/config";
import type { AxiosError } from 'axios';
type Responsable = { idUsuario?: number; idTipoUsuario?: number; tipoUsuario?: string | null; token?: string; tokenArchivo?: string };
type Alumno = { nombre?: string; usuario?: string };
type Alumno = { nombre?: string; idUsuario?: number };
export default function Page() {
const router = useRouter();
@@ -16,6 +16,8 @@ export default function Page() {
const [idServicio, setIdServicio] = useState<number | null>(null);
const [alumno, setAlumno] = useState<Alumno>({});
const [responsable, setResponsable] = useState<Responsable>({});
// Para verificar que se guardo el idServicio
const [isReady, setIsReady] = useState(false);
const updateIsLoading = (v: boolean) => setIsLoading(v);
@@ -54,9 +56,17 @@ export default function Page() {
const tipoUsuario = localStorage.getItem('tipoUsuario');
const token = localStorage.getItem('token') || undefined;
const tokenArchivo = localStorage.getItem('token') || undefined;
setResponsable({ idUsuario: Number.isNaN(idUsuario) ? undefined : idUsuario, idTipoUsuario: Number.isNaN(idTipoUsuario) ? undefined : idTipoUsuario, tipoUsuario, token: token ?? undefined, tokenArchivo: tokenArchivo ?? undefined });
setResponsable({
idUsuario: Number.isNaN(idUsuario) ? undefined : idUsuario,
idTipoUsuario: Number.isNaN(idTipoUsuario) ? undefined : idTipoUsuario,
tipoUsuario,
token: token ?? undefined,
tokenArchivo: tokenArchivo ?? undefined
});
const s = Number(localStorage.getItem('idServicio'));
setIdServicio(Number.isNaN(s) ? null : s);
setIsReady(true);
};
const obtenerRegistro = async () => {
@@ -64,8 +74,12 @@ export default function Page() {
updateIsLoading(true);
try {
const headers = responsable.token ? { Authorization: `Bearer ${responsable.token}` } : undefined;
const res = await axiosInstance.get<{ Usuario?: Alumno; Status?: { idStatus?: number } }>(`/servicio/admin?idServicio=${idServicio}`, { headers });
setAlumno(res.data.Usuario ?? {});
const res = await axiosInstance.get<{ usuario?: Alumno; Status?: { idStatus?: number } }>(`/servicio/admin/${idServicio}`);
console.log('Respuesta al obtener el servicio:', res.data);
console.log("Info Usuario: ", res.data.usuario)
console.log("Nombre del alumno: ", res.data.usuario?.nombre);
setAlumno(res.data.usuario ?? {});
updateIsLoading(false);
const s = res.data.Status?.idStatus ?? 0;
// Se comennto para hacer pruebas
@@ -82,20 +96,28 @@ export default function Page() {
useEffect(() => { getLocalhostInfo(); }, []);
useEffect(() => {
if (responsable.idTipoUsuario === 1) router.push('/admin');
if (responsable.idTipoUsuario === 3) router.push('/alumno');
if (responsable.idTipoUsuario === 4) router.push('/casoEspecial');
if (!isReady) return; // Para esperar a que cargue la info del localStorage
if (responsable.idTipoUsuario === 1) router.replace('/admin');
if (responsable.idTipoUsuario === 3) router.replace('/alumno');
if (responsable.idTipoUsuario === 4) router.replace('/casoEspecial');
if (idServicio !== null) {
console.log("ID Servicio no es nulo, se obtiene el registro.", idServicio);
obtenerRegistro();
} else (
router.replace('/responsable')
)
// Se comento para hacer pruebas
//if (idServicio === null) router.push('/responsable');
else if (idServicio !== null) obtenerRegistro();
// else if (idServicio !== null) obtenerRegistro();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [responsable, idServicio]);
}, [isReady, responsable, idServicio]);
return (
<section className="container px-2 pb-6">
<BotonRegresar />
<h3 className="title">{alumno.nombre} {alumno.usuario}</h3>
<h3 className="title">{alumno.nombre}</h3>
{idServicio !== null && (
<UploadArchivo
+24 -7
View File
@@ -19,6 +19,10 @@ export default function Page() {
const [idServicio, setIdServicio] = useState<number | null>(null);
const [alumno, setAlumno] = useState<Record<string, unknown> | null>(null);
const [responsable, setResponsable] = useState<Responsable>({});
// Para esperar a que cargue la info del localStorage
const [isReady, setIsReady] = useState(false);
const updateIsLoading = (v: boolean) => setIsLoading(v);
@@ -37,7 +41,7 @@ export default function Page() {
const anyErr = err as { err?: unknown };
if (anyErr.err === 'token error') {
try { localStorage.clear(); } catch (_) {}
router.push('/');
router.replace('/');
}
}
};
@@ -59,17 +63,24 @@ export default function Page() {
const idTipoUsuario = Number(localStorage.getItem('idTipoUsuario'));
const tipoUsuario = localStorage.getItem('tipoUsuario');
const token = localStorage.getItem('token') || undefined;
setResponsable({ idUsuario: Number.isNaN(idUsuario) ? undefined : idUsuario, idTipoUsuario: Number.isNaN(idTipoUsuario) ? undefined : idTipoUsuario, tipoUsuario, token: token ?? undefined });
setResponsable({
idUsuario: Number.isNaN(idUsuario) ? undefined : idUsuario,
idTipoUsuario: Number.isNaN(idTipoUsuario) ? undefined : idTipoUsuario,
tipoUsuario,
token: token ?? undefined
});
const s = Number(localStorage.getItem('idServicio'));
setIdServicio(Number.isNaN(s) ? null : s);
setIsReady(true);
};
const obtenerRegistro = async () => {
if (!idServicio) return;
updateIsLoading(true);
try {
const headers = responsable.token ? { Authorization: `Bearer ${responsable.token}` } : undefined;
const res = await axiosInstance.get<{ Usuario?: Record<string, unknown>; idCuestionarioPrograma?: number }>(`/servicio/admin?idServicio=${idServicio}`, { headers });
//const headers = responsable.token ? { Authorization: `Bearer ${responsable.token}` } : undefined;
const res = await axiosInstance.get<{ Usuario?: Record<string, unknown>; idCuestionarioPrograma?: number }>(`/servicio/admin/${idServicio}`,);
setAlumno(res.data.Usuario ?? null);
updateIsLoading(false);
if (res.data.idCuestionarioPrograma) router.push('/responsable');
@@ -90,14 +101,20 @@ export default function Page() {
}, []);
useEffect(() => {
// Hacemos la validacion para asegurarnos de que cargo la info del local
if (!isReady) return; // Para esperar a que cargue la info del localStorage
// Redireccionamientos segun tipo de usuario
if (responsable.idTipoUsuario === 1) router.push('/administrador');
if (responsable.idTipoUsuario === 3) router.push('/alumno');
if (responsable.idTipoUsuario === 4) router.push('/casoEspecial');
// Para verificar el servicio
//if (idServicio === null) router.push('/responsable'); // Esta mal la logica
else if (idServicio !== null) obtenerRegistro();
if (idServicio !== null) {
obtenerRegistro();
} else router.push('/responsable');
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [responsable, idServicio]);
}, [isReady, responsable, idServicio]);
return (
<section className="container my-5 px-3">
@@ -110,7 +127,7 @@ export default function Page() {
</p>
<p>
<a href="https://www.acatlan.unam.mx/normatividad" target="_blank" rel="noreferrer"> www.acatlan.unam.mx/normatividad</a>
<a href="https://www.acatlan.unam.mx/normatividad" target="_blank" rel="noreferrer" className="text-decoration-none text-morado"> www.acatlan.unam.mx/normatividad</a>
</p>
</div>
+3 -3
View File
@@ -27,7 +27,7 @@ export default function Page() {
const anyErr = err as { err?: unknown };
if (anyErr.err === 'token error') {
try { localStorage.clear(); } catch (_) {}
router.push('/');
router.replace('/');
}
}
};
@@ -59,7 +59,7 @@ export default function Page() {
}, []);
useEffect(() => {
if (responsable.idTipoUsuario === 1) router.push('/admin');
if (responsable.idTipoUsuario === 1) router.push('/administrador');
if (responsable.idTipoUsuario === 3) router.push('/alumno');
if (responsable.idTipoUsuario === 4) router.push('/casoEspecial');
// eslint-disable-next-line react-hooks/exhaustive-deps
@@ -81,7 +81,7 @@ export default function Page() {
</p>
</div>
<h2 className="title">Añadir Servicio Social</h2>
<h2 className="title fw-bold mt-4 mb-4">Añadir Servicio Social</h2>
<NuevoServicio
responsable={responsable}
+12 -9
View File
@@ -53,17 +53,20 @@ export default function Page() {
};
useEffect(() => {
const r: Responsable = {
idUsuario: Number(localStorage.getItem('idUsuario')) || undefined,
idTipoUsuario: Number(localStorage.getItem('idTipoUsuario')) || undefined,
//tipoUsuario: localStorage.getItem('tipoUsuario') || undefined,
token: localStorage.getItem('token') || undefined,
};
const r: Responsable = {
idUsuario: Number(localStorage.getItem('idUsuario')) || undefined,
idTipoUsuario: Number(localStorage.getItem('idTipoUsuario')) || undefined,
//tipoUsuario: localStorage.getItem('tipoUsuario') || undefined,
token: localStorage.getItem('token') || undefined,
};
setResponsable(r);
console.log('Responsable:', r);
console.log('id Usuario en componente padrre', r.idUsuario);
//ontenerCatalogoStatus();
console.log("Esta es la infromacino del useState en responsable", responsable)
if (r.idTipoUsuario === 1) router.push('/administrador');
if (r.idTipoUsuario === 3) router.push('/alumno');
if (r.idTipoUsuario === 4) router.push('/casoEspecial');
@@ -75,8 +78,8 @@ export default function Page() {
), [responsable]);
return (
<section className="container px-2 pb-6">
<div className="pt-6 pb-5">
<section className="container px-2 pb-6 mb-2">
<div className="pb-3">
<p className="is-size-4 block mt-5 h4 mb-4">Estimado(a) responsable del programa de servicio social:</p>
<p className="block">
@@ -85,7 +88,7 @@ export default function Page() {
</div>
<div className="pb-3">
<button className="button is-info" onClick={() => router.push('/responsable/nuevo')}>Agregar alumno</button>
<button className="button is-info rounded-2" onClick={() => router.push('/responsable/nuevo')}>Agregar alumno</button>
</div>
{tabla}
+81 -18
View File
@@ -4,7 +4,8 @@ import { useState } from "react";
import { useRouter } from "next/navigation";
import { axiosInstance } from '@/api/config';
import { isAxiosError } from 'axios';
import type { AxiosResponse } from 'axios';
import type { AxiosError, AxiosResponse } from 'axios';
import Swal from "sweetalert2";
interface Status {
idStatus: number;
@@ -25,7 +26,7 @@ interface Datos {
//Programa?: Programa
//Carrera?: Carrera;
//Usuario?: Usuario;
Status?: Status;
status?: Status;
creditos?: string;
correo?: string;
fechaRegistro?: string;
@@ -57,7 +58,7 @@ interface Props {
}
interface ApiResponse {
data: { message: string };
message: string;
}
interface RechazarData {
@@ -97,6 +98,11 @@ export default function Archivo({
};
const rechazarArchivo = () => {
// Depuramos para ver errores
console.log("Title", title)
console.log("status", datos.status?.idStatus)
console.log("idServicio", datos.idServicio)
switch (title) {
case "carta de aceptación":
rechazarFunc(rechazarCartaAceptacion);
@@ -118,18 +124,36 @@ export default function Archivo({
mensaje: mensajeRechazo,
};
console.log("Datos para rechazar:", data);
try {
updateIsLoading(true);
const res = await funcRechazar(data);
localStorage.removeItem("idServicio");
imprimirMensaje(res.data.data.message);
router.push("/administrador");
console.log("Respuesta al rechazar:", res.data);
const mensaje = res.data?.message || "Documento rechazado exitosamente";
Swal.fire({
title: 'Exito',
text: mensaje,
icon: 'success',
})
// imprimirMensaje(mensaje);
router.replace("/administrador");
} catch (err: unknown) {
if (isAxiosError(err)) {
imprimirError(err.response?.data || err.message);
} else {
imprimirError(err);
}
const axiosErr = err as AxiosError<any>;
const mensaje = axiosErr?.response?.data?.message || 'No se pudo rechazar el documento'
Swal.fire({
title: 'Error',
text: mensaje,
icon: 'error',
})
// if (isAxiosError(err)) {
// imprimirError(err.response?.data || err.message);
// } else {
// imprimirError(err);
// }
} finally {
updateIsLoading(false);
}
@@ -145,13 +169,46 @@ export default function Archivo({
axiosInstance.put(`/servicio/rechazar_informe`, data);
return (
<div className="mb-4">
<div className="flex items-center">
<h6 className="pr-4 text-lg">Ver {title}</h6>
<div className="container mb-4">
<div className="d-flex align-items-center">
<div className="pr-4">
{/* Texto */}
<h5 className="mb-0">
Ver {title}
</h5>
<div className="d-flex align-items-center gap-2">
<a
className="btn btn-link btn-light text-decoration-none ms-3 text-morado"
target="_blank"
rel="noopener noreferrer"
href={`https://drive.google.com/file/d/${archivo()}/view?usp=sharing`}
style={{ background: '#eae4f8'}}
>
Ver
</a>
{((datos.status?.idStatus === 1 && title === "carta de aceptación") ||
(datos.status?.idStatus === 5 &&
(title === "carta de termino" || title === "informe global"))) && (
<button
className="btn btn-danger"
onClick={updateRechazar}
>
Rechazar
</button>
)}
</div>
{/* Solo de moemento para la parte visual falta corregir esto */}
{/*
<div className="pr-4">
<a
className="btn btn-link btn-light"
className="btn btn-link btn-light text-decoration-none"
target="_blank"
rel="noopener noreferrer"
href={`https://drive.google.com/file/d/${archivo()}/view?usp=sharing`}
@@ -159,9 +216,10 @@ export default function Archivo({
Ver
</a>
</div>
*/}
{((datos.Status?.idStatus === 1 && title === "carta de aceptación") ||
(datos.Status?.idStatus === 5 &&
{/* {((datos.status?.idStatus === 1 && title === "carta de aceptación") ||
(datos.status?.idStatus === 5 &&
(title === "carta de termino" || title === "informe global"))) && (
<button
className="btn btn-danger"
@@ -170,11 +228,16 @@ export default function Archivo({
Rechazar
</button>
)}
</div> */}
</div>
{rechazar && (
<div className="mt-2">
<label>Razón del rechazo:</label>
<label>Razón del rechazo (No olvide indicar el documento rechazado):</label>
<textarea
maxLength={500}
value={mensajeRechazo}
@@ -2,8 +2,9 @@
import { useState } from "react";
import { axiosInstance } from '@/api/config';
import { isAxiosError } from 'axios';
import { AxiosError } from 'axios';
import { useRouter } from "next/navigation";
import Swal from "sweetalert2";
interface Status {
idStatus: number;
@@ -80,61 +81,71 @@ export default function CancelarServicio({
updateIsLoading(true);
const res = await axiosInstance.put(`/servicio/cancelar`, data);
localStorage.removeItem("idServicio");
imprimirMensaje(res.data.message);
router.push("/admin");
Swal.fire('Exito', res.data.message, 'success')
// imprimirMensaje(res.data.message);
router.push("/administrador");
} catch (err: unknown) {
if (isAxiosError(err)) imprimirError(err.response?.data || err.message);
else if (err instanceof Error) imprimirError(err.message);
else imprimirError(err);
const axiosErr = err as AxiosError<any>;
const mensaje = axiosErr?.response?.data?.message || 'No se pudo cancelar el servicio, intentelo mas tarde.';
Swal.fire({
icon: 'error',
title: 'Error',
text: mensaje,
})
// if (isAxiosError(err)) imprimirError(err.response?.data || err.message);
// else if (err instanceof Error) imprimirError(err.message);
// else imprimirError(err);
} finally {
updateIsLoading(false);
}
};
return (
<div className="mt-5 space-y-4">
{/* Botón de Cancelar */}
<button
disabled={datos.Status?.idStatus === 6 || datos.Status?.idStatus === 10}
onClick={updateCancelar}
className={`px-4 py-2 rounded text-white ${
datos.Status?.idStatus === 6 || datos.Status?.idStatus === 10
? "bg-gray-400 cursor-not-allowed"
: "bg-red-600 hover:bg-red-700"
}`}
>
Cancelar
</button>
{/* Área de mensaje */}
{cancelar && (
<div className="space-y-2">
<label className="block font-medium">Razón de la cancelación:</label>
<textarea
maxLength={500}
value={mensajeCancelar}
onChange={(e) => setMensajeCancelar(e.target.value)}
className="border rounded p-2 w-full"
/>
<div className="container mt-5 space-y-4">
{/* Botón de Cancelar */}
<button
disabled={!mensajeCancelar}
onClick={() =>
imprimirWarning(
"¿Seguro(a) que quiere cancelar este servicio?",
cancelarServicio
)
}
className={`px-4 py-2 rounded text-white ${
!mensajeCancelar
disabled={datos.Status?.idStatus === 6 || datos.Status?.idStatus === 10}
onClick={updateCancelar}
className={`px-4 py-2 bg-danger rounded text-white${
datos.Status?.idStatus === 6 || datos.Status?.idStatus === 10
? "bg-gray-400 cursor-not-allowed"
: "bg-blue-600 hover:bg-blue-700"
: "bg-red-600 hover:bg-red-700"
}`}
>
Cancelar servicio
Cancelar
</button>
</div>
)}
{/* Área de mensaje */}
{cancelar && (
<div className="space-y-2 mt-3">
<label className="block font-medium mb-2">Razón de la cancelación:</label>
<textarea
maxLength={500}
value={mensajeCancelar}
onChange={(e) => setMensajeCancelar(e.target.value)}
className="form-control border rounded p-2 w-full"
/>
<button
disabled={!mensajeCancelar}
onClick={() =>
imprimirWarning(
"¿Seguro(a) que quiere cancelar este servicio?",
cancelarServicio
)
}
className={`bg-morado mt-3 mb-4 px-4 py-2 rounded text-white ${
!mensajeCancelar
? "bg-gray-400 cursor-not-allowed"
: "bg-blue-600 hover:bg-blue-700"
}`}
>
Cancelar servicio
</button>
</div>
)}
</div>
);
}
+46 -12
View File
@@ -1,19 +1,30 @@
'use client'
import { axiosInstance } from "@/api/config";
import { CargaMasivaProps } from "@/types/responses";
import { AxiosError } from "axios";
import { useRouter } from "next/navigation";
import React, { useState } from "react";
import { Button, Form } from "react-bootstrap";
import { FaUpload } from "react-icons/fa";
import { FaUpload } from "react-icons/fa6";
import Swal from "sweetalert2";
export default function CargaMasiva({datos}: {datos: CargaMasivaProps}) {
const router = useRouter();
const [csv, setCsv] = useState<File | null>(null)
const url = process.env.NEXT_PUBLIC_API_URL
const link = `${url}/plantilla.csv`
//const url = process.env.NEXT_PUBLIC_API_URL
//const link = `${url}/plantilla.csv`
const handleDowload = () => {
console.log("Esta entrando a la funcion");
const link = document.createElement("a");
link.href = "/plantilla/plantilla.csv";
link.download = "plantilla.csv";
link.click();
}
//const [link] = useState(`${url}/plantilla.csv`)
//console.log("Esta es la url para descargar la plantilla", link);
const validarExt = (file: File | null) => {
if (!file) return
const extPermitidas = /(.csv)$/i
@@ -38,18 +49,40 @@ export default function CargaMasiva({datos}: {datos: CargaMasivaProps}) {
//updateIsLoading(true)
try {
const res = await axiosInstance.post("/programa/carga_masiva", formData,{
console.log("Este es el archivo csv", formData)
// Cambioamos a variable de entorno
const res = await axiosInstance.post(`${process.env.NEXT_PUBLIC_API_URL}/programa/carga_masiva`, formData,{
headers: {
Authorization: `Bearer ${datos.admin.tokenArchivo}`,
'Content-Type': 'multipart/form-data',
},
})
console.log('Respuesta de carga masiva:', res.data)
//updateIsLoading(false)
//ImprimirMensaje(res.data.message)
router.push("/administrador/responsables")
const message = res?.data?.message || 'Se completo la carga masiva con exito.';
Swal.fire({
title: 'Archivo enviado',
text: message, //'El archivo se subió correctamente'
icon: 'success',
});
setCsv(null);
//alert("Se subio la carga masiva correctamente.");
//router.push("/administrador/responsables")
} catch (error) {
//updateIsLoading(false)
//imprimirError(error.response.data || error)
const axiosErr = error as AxiosError<any>;
const mensaje = axiosErr?.response?.data?.message || 'Error en la carga masiva.'
Swal.fire({
title: 'Error al enviar el archivo',
text: mensaje,
icon: 'error',
})
setCsv(null)
console.log('Error en carga masiva:', error)
}
}
@@ -70,7 +103,7 @@ export default function CargaMasiva({datos}: {datos: CargaMasivaProps}) {
{csv?.name || 'Arrastra aquí tu archivo o da click aquí para buscar'}
</p>
<p className="is-size-6">Tamaño máximo 20MB</p>
<p className="is-size-7">Si al momento de elegir uin archivo este no se selecciona, haga click en cancelar en la ventana emergente e intente de nuevo.</p>
<p className="is-size-7">Si al momento de elegir un archivo este no se selecciona, haga click en cancelar en la ventana emergente e intente de nuevo.</p>
</div>
<input
id="csvInput"
@@ -86,16 +119,17 @@ export default function CargaMasiva({datos}: {datos: CargaMasivaProps}) {
disabled={!csv}
onClick={enviarCargaMasiva}>
Enviar archivo
</Button>
</Button>
<Button
href={link}
target="_blank"
rel="noopener noreferrer"
<Button
type="button"
onClick={handleDowload}
// target="_blank"
// rel="noopener noreferrer"
className="bg-morado border-morado"
>
Descargar plantilla
</Button>
</Button>
</div>
</div>
);
@@ -1,9 +1,10 @@
import { useState } from "react";
import { isAxiosError } from 'axios';
import { axiosInstance } from '@/api/config';
import type { AxiosResponse } from 'axios';
import { AxiosError, AxiosResponse } from 'axios';
import { useRouter } from "next/navigation";
import { Carrera, Usuario } from "@/types/responses";
import Swal from "sweetalert2";
interface Status {
idStatus: number;
@@ -11,6 +12,8 @@ interface Status {
interface Programa {
acatlan?: boolean;
clavePrograma?: string;
programa?: string;
}
interface Alumno {
@@ -24,10 +27,10 @@ interface Admin {
}
interface Datos {
Programa?: Programa;
programa?: Programa;
//Carrera?: Carrera;
//Usuario?: Usuario;
Status?: Status;
status?: Status;
creditos?: string;
correo?: string;
fechaRegistro?: string;
@@ -83,9 +86,11 @@ export default function ConfirmarServicio({
const [vistoBuenoAcatlan, setVistoBuenoAcatlan] = useState(false);
const router = useRouter();
console.log("Datos resividos de datod", datos)
const confirmarServicio = () => {
if (datos.Status?.idStatus === 1) confirmar(confirmarPreRegistro);
else if (datos.Status?.idStatus === 5) confirmar(confirmarLiberacion);
if (datos.status?.idStatus === 1) confirmar(confirmarPreRegistro);
else if (datos.status?.idStatus === 5) confirmar(confirmarLiberacion);
};
const confirmar = async (
@@ -97,20 +102,33 @@ export default function ConfirmarServicio({
updateIsLoading(true);
const res = await funcConfirmar(data);
// ✅ TS sabe que res.data.message existe
imprimirMensaje(res.data.message);
router.push("/admin");
Swal.fire({
icon: 'success',
title: 'Exito',
text: res.data.message,
});
// imprimirMensaje(res.data.message);
router.replace("/administrador");
} catch (err: unknown) {
if (isAxiosError(err)) {
imprimirError(err.response?.data || err.message);
} else {
imprimirError(err);
}
const axiosErr = err as AxiosError<any>;
const mensaje = axiosErr?.response?.data?.message || 'No se pudo confirmar el servicio, por favor intentelo mas tarde.';
Swal.fire({
icon: 'error',
title: 'Error',
text: mensaje,
});
// if (isAxiosError(err)) {
// imprimirError(err.response?.data || err.message);
// } else {
// imprimirError(err);
// }
} finally {
updateIsLoading(false);
}
};
const confirmarPreRegistro = (data: ConfirmarData) =>
const confirmarPreRegistro = (data: ConfirmarData) =>
axiosInstance.put<ApiResponse>(`/servicio/registro`, data);
const confirmarLiberacion = (data: ConfirmarData) => {
@@ -118,9 +136,29 @@ export default function ConfirmarServicio({
return axiosInstance.put<ApiResponse>(`/servicio/liberacion`, data);
};
// Validar las claves permitidas en un arreglo
const clavesPermitidas = process.env.NEXT_PUBLIC_CP?.split(',').map(c => c.trim()) || [];
const confirma = () => {
Swal.fire({
title: "¿Seguro(a) que quieres confirmar este servicio?",
icon: 'warning',
showCancelButton: true,
confirmButtonText: 'confirmar',
cancelButtonText: 'cancelar',
confirmButtonColor: "#0d6efd",
cancelButtonColor: "#dc3545",
}).then((result) => {
if (result.isConfirmed) {
confirmarServicio();
}
})
}
return (
<div className="space-y-4">
{datos.Status?.idStatus === 5 && datos.Programa?.acatlan && (
// datos.Programa?.acatlan && datos.programa?.programa === clavesPermitidas &&
<div className="container space-y-4">
{datos.status?.idStatus === 5 && clavesPermitidas.includes(datos?.programa?.clavePrograma || '') && (
<div className="flex items-center space-x-2">
<input
type="checkbox"
@@ -129,28 +167,23 @@ export default function ConfirmarServicio({
onChange={(e) => setVistoBuenoAcatlan(e.target.checked)}
className="form-checkbox h-5 w-5 text-blue-600"
/>
<label htmlFor="vistoBuenoAcatlan" className="text-gray-700">
<label htmlFor="vistoBuenoAcatlan" className="text-gray-700 mb-4 me-2 ms-2">
Visto bueno Acatlán
</label>
</div>
)}
{(datos.Status?.idStatus === 1 || datos.Status?.idStatus === 5) && (
{(datos.status?.idStatus === 1 || datos.status?.idStatus === 5) && (
<button
disabled={
datos.Status?.idStatus === 5 &&
datos.Programa?.acatlan &&
datos.status?.idStatus === 5 &&
datos.programa?.acatlan &&
!vistoBuenoAcatlan
}
onClick={() =>
imprimirWarning(
"¿Seguro(a) que quieres confirmar este servicio?",
confirmarServicio
)
}
onClick={confirma}
className={`px-4 py-2 rounded text-white ${
datos.Status?.idStatus === 5 &&
datos.Programa?.acatlan &&
datos.status?.idStatus === 5 &&
datos.programa?.acatlan &&
!vistoBuenoAcatlan
? "bg-gray-400 cursor-not-allowed"
: "bg-green-600 hover:bg-green-700"
+84 -18
View File
@@ -1,7 +1,9 @@
"use client"
import { axiosInstance } from "@/api/config";
import { error } from "console";
import { useState } from "react";
import { Button, Col, FormGroup, FormLabel, FormSelect, InputGroup } from "react-bootstrap";
import Swal from "sweetalert2";
interface Props {
years: number[];
@@ -10,26 +12,88 @@ interface Props {
}
export default function Cuestionario({ years, admin, updateIsLoading }: Props) {
const [selectedCuestionario, setSelectedCuestionario] = useState("");
const [selectedCuestionario, setSelectedCuestionario] = useState({});
const [version, setVersion] = useState("");
const [selectedYear, setSelectedYear] = useState("");
const downloadExcel = async () => {
try {
//updateIsLoading(true);
//const res = await axiosInstance.get(`/cuestionario_alumno?year=${selectedCuestionario}&version=${version}`, {
// Hacer la validacion de que tipo de cuestionario quiere
// if(selectedCuestionario === "cuestionario_alumno") {
// var res = await axiosInstance.get(`/cuestionario-alumno2`, {
// params: { anio: selectedYear, version },
// responseType: "blob",
// });
// } else if(selectedCuestionario === "cuestionario_programa") {
// var res = await axiosInstance.get(`/cuestionario-programa2`, {
// params: { anio: selectedYear, version },
// responseType: "blob",
// });
// } else {
// Swal.fire({
// title: 'Error',
// text: 'Seleccione un cuestionario válido.',
// icon: 'error',
// })
// return;
// }
// antes
console.log("Estos son los datos que se van a enviar para descargar el cuestionario", { selectedCuestionario, version, selectedYear })
const res = await axiosInstance.get(`/${selectedCuestionario}`, {
params: { year: selectedYear, version },
params: { anio: selectedYear, version },
responseType: "blob",
});
console.log("Esrta es la respuesta", res)
console.log("Esta es la infromacino de la respuesta", res.data)
//saveAs(res.data, `${selectedYear}_${selectedCuestionario}_${version}.csv`);
// Crear blob manualmente
const blob = new Blob([res.data], { type: "text/csv" });
// Crear URL temporal para descargar
const url = window.URL.createObjectURL(blob);
// Crear link "virtual"
const link = document.createElement("a");
link.href = url;
link.download = `${selectedYear}_${selectedCuestionario}_${version}.csv`;
// Disparar descarga
document.body.appendChild(link);
link.click();
// limpiar URL temporal
link.remove();
window.URL.revokeObjectURL(url);
Swal.fire({
title: 'Éxito',
text: 'Se descargo el cuestionario exitosamente.',
icon: 'success',
})
// reset
setSelectedYear("");
setSelectedCuestionario("");
setVersion("");
//updateIsLoading(false);
} catch (err: unknown) {
} catch (err) {
Swal.fire({
title: 'Error',
text: 'Error al descargar el cuestionario.',
icon: 'error',
})
console.error("Error en descargar el formulario", err)
//updateIsLoading(false);
// optional: handle error, e.g. console.error(err)
}
@@ -42,11 +106,12 @@ export default function Cuestionario({ years, admin, updateIsLoading }: Props) {
<div>
<Col>
<FormGroup>
<FormLabel>Cuestionario:</FormLabel>
<FormLabel className="fw-semibold">Cuestionario:</FormLabel>
<InputGroup>
<FormSelect>
<option value="cuestionario_alumno">Cuestionario de Alumnos</option>
<option value="cuestionario_programa">Cuestionario de Programas</option>
<FormSelect onChange={(e) => setSelectedCuestionario(e.target.value)}>
<option value="">Seleccione un cuestionario:</option>
<option value="cuestionario-alumno2">Cuestionario de Alumnos</option>
<option value="cuestionario-programa2">Cuestionario de Programas</option>
</FormSelect>
</InputGroup>
</FormGroup>
@@ -54,9 +119,10 @@ export default function Cuestionario({ years, admin, updateIsLoading }: Props) {
<Col>
<FormGroup>
<FormLabel>Version:</FormLabel>
<FormLabel className="fw-semibold">Version:</FormLabel>
<InputGroup>
<FormSelect>
<FormSelect value={version} onChange={(e) => setVersion(e.target.value)}>
<option value="">Seleccione una version:</option>
<option value="v1">V1</option>
<option value="v2">V2</option>
</FormSelect>
@@ -66,15 +132,15 @@ export default function Cuestionario({ years, admin, updateIsLoading }: Props) {
<Col>
<FormGroup>
<FormLabel>Año:</FormLabel>
<FormLabel className="fw-semibold">Año:</FormLabel>
<InputGroup>
<FormSelect value={selectedYear} onChange={(e) => setSelectedYear(e.target.value)}>
<option value="">Seleccione un año:</option>
{years.map((y, i) => (
<option key={i} value={y}>
{y}
</option>
))}
<option value="">Seleccione un año:</option>
{years.map((y, i) => (
<option key={i} value={y}>
{y}
</option>
))}
</FormSelect>
</InputGroup>
</FormGroup>
@@ -82,10 +148,10 @@ export default function Cuestionario({ years, admin, updateIsLoading }: Props) {
<div className="d-flex gap-2 mt-4 mb-3">
<Button
disabled={!selectedYear || !selectedCuestionario || !version}
//disabled={!selectedYear || !selectedCuestionario || !version}
onClick={downloadExcel}
>
Enviar archivo
Descragar Excel
</Button>
</div>
</div>
@@ -8,6 +8,12 @@ import validator from "validator";
import DatePicker from "react-datepicker";
import "react-datepicker/dist/react-datepicker.css";
import { useRouter } from "next/navigation";
import { registerLocale } from "react-datepicker";
import { es } from "date-fns/locale/es";
import { Col, FormGroup, FormLabel, InputGroup } from "react-bootstrap";
import { FaRegCalendar } from "react-icons/fa6";
registerLocale("es", es);
interface Viejo {
correo?: string;
@@ -22,7 +28,9 @@ interface Viejo {
}
interface Admin {
token: { headers: Record<string, string> };
idTipoUsuario: number;
tipoUsuario?: string;
token: string;
}
interface Props {
@@ -84,7 +92,12 @@ export default function EditarAlumno({
};
const actualizar = async () => {
const data: Record<string, unknown> = { idCasoEspecial };
const data: Record<string, unknown> = { idCasoEspecial };
if (!data.idCasoEspecial) {
console.log('No tiene el id')
return;
}
if (nuevo.direccion) data.direccion = nuevo.direccion;
if (nuevo.correo) data.correo = nuevo.correo;
@@ -92,16 +105,18 @@ export default function EditarAlumno({
if (nuevo.motivo) data.motivo = nuevo.motivo;
if (nuevo.dependencia) data.dependencia = nuevo.dependencia;
if (nuevo.institucion) data.institucion = nuevo.institucion;
if (nuevo.fechaInicio) data.fechaInicio = moment(nuevo.fechaInicio).toDate();
if (nuevo.fechaFin) data.fechaFin = moment(nuevo.fechaFin).toDate();
if (nuevo.fechaInicio) data.fechaInicio = formatDate(nuevo.fechaInicio);
if (nuevo.fechaFin) data.fechaFin = formatDate(nuevo.fechaFin);
if (nuevo.fechaNacimiento)
data.fechaNacimiento = moment(nuevo.fechaNacimiento).toDate();
data.fechaNacimiento = formatDate(nuevo.fechaNacimiento);
console.log('Esta es la info', data);
try {
updateIsLoading(true);
const res = await axiosInstance.put(`/caso_especial/update`, data, admin.token);
const res = await axiosInstance.put(`/caso-especial/update/${idCasoEspecial}`, data);
imprimirMensaje(res.data.message);
router.push("/admin/casos_especiales/caso_especial");
router.push("/administrador/casos_especiales");
} catch (err: unknown) {
let msg: unknown = err;
if (isAxiosError(err) && err.response?.data) {
@@ -110,65 +125,139 @@ export default function EditarAlumno({
msg = err.message;
}
imprimirError(msg);
router.replace('/adminsitrador/casos_especiales')
} finally {
updateIsLoading(false);
}
};
const formatDate = (date: Date) => {
return date.toISOString().split("T")[0];
};
return (
<div className="space-y-4 mt-6">
<h3 className="text-2xl font-semibold mb-4">Editar información del alumno</h3>
<h2 className="fw-bold mb-4">Editar información del alumno</h2>
{/* Correo */}
<div>
<label className="block mb-1 font-medium">Correo electrónico</label>
<div className="mb-3">
<label className="form-label fw-semibold block mb-1 font-medium">Correo electrónico</label>
<input
type="email"
placeholder={viejo.correo}
value={nuevo.correo || ""}
onChange={(e) => setNuevo({ ...nuevo, correo: e.target.value })}
className="border rounded p-2 w-full"
className="form-control border rounded p-2 w-full"
/>
</div>
{/* Teléfono */}
<div>
<label className="block mb-1 font-medium">Teléfono</label>
<div className="mb-3">
<label className="form-label fw-semibold block mb-1 font-medium">Teléfono</label>
<input
type="tel"
maxLength={10}
placeholder={viejo.telefono}
value={nuevo.telefono || ""}
onChange={(e) => setNuevo({ ...nuevo, telefono: e.target.value })}
className="border rounded p-2 w-full"
className="form-control border rounded p-2 w-full"
/>
</div>
{/* Dirección */}
<div>
<label className="block mb-1 font-medium">Dirección</label>
<div className="mb-3">
<label className="form-label fw-semibold block mb-1 font-medium">Dirección</label>
<input
type="text"
placeholder={viejo.direccion}
value={nuevo.direccion || ""}
onChange={(e) => setNuevo({ ...nuevo, direccion: e.target.value })}
className="border rounded p-2 w-full"
className="form-control border rounded p-2 w-full"
/>
</div>
<Col>
<FormGroup>
<FormLabel>
Fecha de inicio
</FormLabel>
<InputGroup>
<InputGroup.Text>
<FaRegCalendar />
</InputGroup.Text>
<DatePicker
selected={nuevo.fechaInicio}
onChange={(date) => setNuevo({ ...nuevo, fechaInicio: date || undefined })}
minDate={minDate1}
placeholderText={fecha(viejo.fechaInicio)}
className="form-control"
wrapperClassName="flex-grow-1"
calendarClassName="mi-calendario"
showMonthDropdown
showYearDropdown
scrollableYearDropdown
yearDropdownItemNumber={60}
dropdownMode="select"
locale="es"
/>
</InputGroup>
</FormGroup>
</Col>
{/* Fecha inicio */}
<div>
<label className="block mb-1 font-medium">Fecha de inicio</label>
{/* <div>
<label className="form-label fw-semibold block mb-1 font-medium">Fecha de inicio</label>
<DatePicker
selected={nuevo.fechaInicio}
onChange={(date) => setNuevo({ ...nuevo, fechaInicio: date || undefined })}
minDate={minDate1}
placeholderText={fecha(viejo.fechaInicio)}
className="border rounded p-2 w-full"
/>
</div>
selected={nuevo.fechaInicio}
onChange={(date) => setNuevo({ ...nuevo, fechaInicio: date || undefined })}
minDate={minDate1}
placeholderText={fecha(viejo.fechaInicio)}
className="form-control"
wrapperClassName="flex-grow-1"
calendarClassName="mi-calendario"
{/* Fecha fin */}
showMonthDropdown
showYearDropdown
scrollableYearDropdown
yearDropdownItemNumber={60}
dropdownMode="select"
locale="es"
/>
</div> */}
<Col>
<FormGroup>
<FormLabel>Fecha de fin</FormLabel>
<InputGroup>
<InputGroup.Text>
<FaRegCalendar />
</InputGroup.Text>
<DatePicker
selected={nuevo.fechaFin}
onChange={(date) => setNuevo({ ...nuevo, fechaFin: date || undefined })}
minDate={minDate2}
placeholderText={fecha(viejo.fechaFin)}
dateFormat={'dd-MM-yyyy'}
className="form-control"
wrapperClassName="flex-grow-1"
calendarClassName="mi-calendario"
showMonthDropdown
showYearDropdown
scrollableYearDropdown
yearDropdownItemNumber={60}
dropdownMode="select"
locale='es'
/>
</InputGroup>
</FormGroup>
</Col>
{/* Fecha fin
{nuevo.fechaInicio && (
<div>
<label className="block mb-1 font-medium">Fecha de fin</label>
@@ -178,29 +267,61 @@ export default function EditarAlumno({
minDate={minDate2}
placeholderText={fecha(viejo.fechaFin)}
className="border rounded p-2 w-full"
locale='es'
/>
</div>
)}
)} */}
<Col>
<FormGroup>
<FormLabel>Fecha de nacimiento</FormLabel>
<InputGroup>
<InputGroup.Text>
<FaRegCalendar />
</InputGroup.Text>
<DatePicker
selected={nuevo.fechaNacimiento}
onChange={(date) => setNuevo({ ...nuevo, fechaNacimiento: date || undefined })}
placeholderText={fecha(viejo.fechaNacimiento)}
dateFormat={'dd-MM-yyyy'}
className="form-control"
wrapperClassName="flex-grow-1"
calendarClassName="mi-calendario"
showMonthDropdown
showYearDropdown
scrollableYearDropdown
yearDropdownItemNumber={60}
dropdownMode="select"
locale='es'
/>
</InputGroup>
</FormGroup>
</Col>
{/* Fecha nacimiento */}
<div>
{/* <div>
<label className="block mb-1 font-medium">Fecha de nacimiento</label>
<DatePicker
selected={nuevo.fechaNacimiento}
onChange={(date) => setNuevo({ ...nuevo, fechaNacimiento: date || undefined })}
placeholderText={fecha(viejo.fechaNacimiento)}
className="border rounded p-2 w-full"
className="form-control border rounded p-2 w-full"
locale='es'
/>
</div>
</div> */}
{/* Motivo */}
{viejo.motivo && (
<div>
<label className="block mb-1 font-medium">Motivo</label>
<div className="mb-3">
<label className="form-label fw-semibold block mb-1 font-medium">Motivo</label>
<select
value={nuevo.motivo || ""}
onChange={(e) => setNuevo({ ...nuevo, motivo: e.target.value })}
className="border rounded p-2 w-full my-2"
className="form-control border rounded p-2 w-full my-2"
>
<option value="">Seleccione...</option>
<option value="1">Tercera edad</option>
@@ -211,28 +332,28 @@ export default function EditarAlumno({
{/* Dependencia */}
{viejo.dependencia && (
<div>
<label className="block mb-1 font-medium">Dependencia</label>
<div className="mb-3">
<label className="form-label fw-semibold block mb-1 font-medium">Dependencia</label>
<input
type="text"
placeholder={viejo.dependencia}
value={nuevo.dependencia || ""}
onChange={(e) => setNuevo({ ...nuevo, dependencia: e.target.value })}
className="border rounded p-2 w-full"
className="form-control border rounded p-2 w-full"
/>
</div>
)}
{/* Institución */}
{viejo.institucion && (
<div>
<label className="block mb-1 font-medium">Institución</label>
<div className="mb-3">
<label className="form-label fw-semibold block mb-1 font-medium">Institución</label>
<input
type="text"
placeholder={viejo.institucion}
value={nuevo.institucion || ""}
onChange={(e) => setNuevo({ ...nuevo, institucion: e.target.value })}
className="border rounded p-2 w-full"
className="form-control border rounded p-2 w-full"
/>
</div>
)}
@@ -5,6 +5,7 @@ import { axiosInstance } from "@/api/config";
import { isAxiosError } from "axios";
import validator from "validator";
import { useRouter } from "next/navigation";
import Swal from "sweetalert2";
interface Responsable {
idUsuario?: number;
@@ -26,6 +27,11 @@ interface Props {
updateIsLoading: (value: boolean) => void;
}
function convertirFecha(fecha: string | Date) {
return new Date(fecha).toISOString().split("T")[0];
}
export default function EditarResponsable({
//admin,
responsable,
@@ -52,25 +58,81 @@ export default function EditarResponsable({
return true;
};
// Funcion para confirmar actualizacion
const confirmarActualizacion = () => {
Swal.fire({
title: '¿Seguro(a) que quiere actualizar la información de este usuario?',
icon: "warning",
showCancelButton: true,
confirmButtonText: "Sí, actualizar",
cancelButtonText: "Cancelar",
confirmButtonColor: "#0d6efd",
cancelButtonColor: "#dc3545",
}).then((result) => {
if (result.isConfirmed) {
actualizar(); // 👈 aquí ejecutas la función
}
});
};
const confirmarCambioPassword = () => {
Swal.fire({
title: '¿Seguro(a) que quiere cambiar la contraseña para este usuario?',
icon: "warning",
showCancelButton: true,
confirmButtonText: "Sí, actualizar",
cancelButtonText: "Cancelar",
confirmButtonColor: "#0d6efd",
cancelButtonColor: "#dc3545",
}).then((result) => {
if (result.isConfirmed) {
password(); // 👈 aquí ejecutas la función
}
});
}
// Actualizar información del responsable
const actualizar = async () => {
const data: Record<string, unknown> = { idUsuario: responsable.idUsuario };
const idResponsable = localStorage.getItem("idResponsable");
if (!idResponsable) return
const data: Record<string, unknown> = { idUsuario: responsable.idUsuario };
if (nuevo.correo) data.correo = nuevo.correo;
if (nuevo.nombre) data.nombre = nuevo.nombre;
// Hacemos el parseo de la fecha
//data.fechaNacimiento = convertirFecha(data.fechaNacimiento);
try {
updateIsLoading(true);
const res = await axiosInstance.put(`/usuario/responsable/update`, data);
imprimirMensaje(res.data.message);
router.push("/administrador/responsables/responsable");
} catch (err: unknown) {
let msg: unknown = err;
if (isAxiosError(err) && err.response?.data) {
msg = err.response.data;
} else if (err instanceof Error) {
msg = err.message;
}
imprimirError(msg);
const res = await axiosInstance.patch(`/usuario/responsable/${idResponsable}`, data);
Swal.fire({
title: 'Éxito',
text: 'Se actualizaron los datos correctamente',
icon: 'success',
})
//imprimirMensaje(res.data.message);
router.replace("/administrador/responsables/responsable");
} catch (err) {
Swal.fire({
title: 'Error',
text: 'Error al actualizar los datos',
icon: 'error',
})
// let msg: unknown = err;
// if (isAxiosError(err) && err.response?.data) {
// console.error("ERROR DEL SERVER:", err.response.data);
// msg = JSON.stringify(err.response.data, null, 2);
// } else if (err instanceof Error) {
// msg = err.message;
// }
// imprimirError(msg);
} finally {
updateIsLoading(false);
}
@@ -80,82 +142,92 @@ export default function EditarResponsable({
const password = async () => {
const data = { idUsuario: responsable.idUsuario };
const idResponsable = localStorage.getItem("idResponsable");
if (!idResponsable) return
try {
updateIsLoading(true);
const res = await axiosInstance.put(`/usuario/new_password_responsable`, data);
imprimirMensaje(res.data.message);
router.push("/administrador/responsables/responsable");
} catch (err: unknown) {
let msg: unknown = err;
if (isAxiosError(err) && err.response?.data) {
msg = err.response.data;
} else if (err instanceof Error) {
msg = err.message;
}
imprimirError(msg);
const res = await axiosInstance.post(`/usuario/new-password-responsable/${idResponsable}`);
Swal.fire({
title: 'Éxito',
text: 'Se envio la nueva contraseña al responsable',
icon: 'success',
})
//imprimirMensaje(res.data.message);
router.replace("/administrador/responsables/responsable");
} catch (err) {
Swal.fire({
title: 'Error',
text: 'Error al actualizar la contraseña del responsable',
icon: 'error',
})
// let msg: unknown = err;
// if (isAxiosError(err) && err.response?.data) {
// msg = err.response.data;
// } else if (err instanceof Error) {
// msg = err.message;
// }
// imprimirError(msg);
} finally {
updateIsLoading(false);
updateIsLoading(false);
}
};
return (
<div className="space-y-4 mt-6">
<h3 className="text-2xl font-semibold mb-4">Editar información del responsable</h3>
<div className="sspace-y-4 mt-6">
<h2 className="text-2xl mb-4 fw-semibold">Editar información del responsable</h2>
{/* Nombre */}
<div>
<label className="block mb-1 font-medium">Nombre</label>
<input
type="text"
placeholder={responsable.nombre}
value={nuevo.nombre || ""}
onChange={(e) => setNuevo({ ...nuevo, nombre: e.target.value })}
className="border rounded p-2 w-full"
/>
</div>
{/* Correo */}
<div>
<label className="block mb-1 font-medium">Correo electrónico</label>
<input
type="email"
placeholder={responsable.usuario}
value={nuevo.correo || ""}
onChange={(e) => setNuevo({ ...nuevo, correo: e.target.value })}
className="border rounded p-2 w-full"
/>
</div>
{/* Nombre */}
<div className="mb-3">
<label className="form-label fw-semibold block mb-1 font-medium">Nombre</label>
<input
type="text"
placeholder={responsable.nombre}
value={nuevo.nombre || ""}
onChange={(e) => setNuevo({ ...nuevo, nombre: e.target.value })}
className="form-control border rounded p-2 w-full"
/>
</div>
{/* Botones */}
<div className="pt-5 flex gap-4">
<button
disabled={mostrarBoton()}
onClick={() =>
imprimirWarning(
"¿Seguro(a) que quiere actualizar la información de este usuario?",
actualizar
)
}
className={`px-4 py-2 rounded text-white ${
mostrarBoton() ? "bg-gray-400 cursor-not-allowed" : "bg-blue-600 hover:bg-blue-700"
}`}
>
Guardar Cambios
</button>
{/* Correo */}
<div>
<label className="form-label block mb-1 font-medium">Correo electrónico</label>
<input
type="email"
placeholder={responsable.usuario}
value={nuevo.correo || ""}
onChange={(e) => setNuevo({ ...nuevo, correo: e.target.value })}
className="form-control border rounded p-2 w-full"
/>
</div>
<button
onClick={() =>
imprimirWarning(
"¿Seguro(a) que quiere cambiar la contraseña para este usuario?",
password
)
}
className="px-4 py-2 rounded bg-blue-500 hover:bg-blue-600 text-white"
>
Cambiar contraseña
</button>
</div>
{/* Botones */}
<div className="pt-5 flex gap-4">
<button
disabled={mostrarBoton()}
onClick={confirmarActualizacion}
className={`mb-4 px-4 py-2 rounded text-white ${
mostrarBoton() ? "bg-gray-400 cursor-not-allowed" : "bg-blue-600 hover:bg-blue-700"
}`}
>
Guardar Cambios
</button>
<button
onClick={confirmarCambioPassword}
className="ms-3 px-4 py-2 rounded bg-morado hover:bg-blue-600 text-white"
>
Cambiar contraseña
</button>
</div>
</div>
);
}
+353 -151
View File
@@ -2,15 +2,22 @@
import { useState, useEffect } from "react";
import { axiosInstance } from "@/api/config";
import { isAxiosError } from 'axios';
import axios, { isAxiosError } from 'axios';
import moment from "moment";
import validator from "validator";
import DatePicker from "react-datepicker";
import "react-datepicker/dist/react-datepicker.css";
import { useRouter } from "next/navigation";
import { Col, FormGroup, FormLabel, InputGroup } from "react-bootstrap";
import { FaRegCalendar, FaUpload } from "react-icons/fa6";
import Swal from "sweetalert2";
import { registerLocale } from "react-datepicker";
import { es } from "date-fns/locale/es";
registerLocale("es", es);
interface Servicio {
Status: { idStatus?: number };
status: { idStatus?: number };
correo?: string;
direccion?: string;
telefono?: string;
@@ -28,7 +35,7 @@ interface AdminToken {
tokenArchivo?: { headers: Record<string, string> };
}; */}
interface Props {
admin: string;
admin: AdminToken;
imprimirError: (err: unknown) => void;
imprimirMensaje: (msg: string) => void;
imprimirWarning: (msg: string, onConfirm: () => void) => void;
@@ -42,8 +49,11 @@ export default function EditarServicio({
imprimirWarning,
updateIsLoading,
}: Props) {
const [selectedInicio, setSelectedInicio] = useState<Date | null>(null);
const [selectFechaFin, setSelectedFechaFin] = useState<Date | null>(null);
const [servicioid, setServicioid] = useState<number>();
const [servicio, setServicio] = useState<Servicio>({ Status: {} });
const [servicio, setServicio] = useState<Servicio>({ status: {} });
const [correo, setCorreo] = useState("");
const [telefono, setTelefono] = useState("");
const [direccion, setDireccion] = useState("");
@@ -60,6 +70,14 @@ export default function EditarServicio({
const fecha = (date?: Date) =>
date ? moment(date).format("DD/MM/YYYY") : "";
const validarFechas = () => {
const fechaInicio = moment(selectedInicio);
const fechaFin = moment(selectFechaFin);
if (!fechaInicio.isValid() || !fechaFin.isValid()) return true;
return false
}
// Validación de archivos
const sizeFileValido = (file: File) => {
if (file.size >= 20000000) {
@@ -80,14 +98,17 @@ export default function EditarServicio({
try {
updateIsLoading(true);
console.log("Obteniendo registro para servicioid al hacer el get:", servicioid);
const res = await axiosInstance.get(`/servicio/admin?idServicio=${servicioid}`);
const res = await axiosInstance.get(`/servicio/admin/${servicioid}`);
console.log("Respuesta del servicio obtenido:", res.data);
const data = res.data;
setServicio(data);
console.log("Esta es la info de la respuesta", res.data)
console.log("Esta es la info de la respuesta con doble data", res.data.data)
// Redirección si el estado no es válido
if (data.Status.idStatus === 6 || data.Status.idStatus === 10) {
router.push("/admin/servicio");
if (data.status.idStatus === 6 || data.status.idStatus === 10) {
router.push("/administrador/servicio");
}
setFechaInicio(new Date(data.fechaInicio));
@@ -120,25 +141,61 @@ export default function EditarServicio({
if (direccion) data.direccion = direccion;
if (correo) data.correo = correo;
if (telefono) data.telefono = telefono;
if (fechaInicio) data.fechaInicio = fechaInicio;
if (fechaFin) data.fechaFin = fechaFin;
if (fechaNacimiento) data.fechaNacimiento = fechaNacimiento;
// Transformamos las fechas para que no muestre error en el back
if (selectedInicio && moment(selectedInicio).isValid()) data.fechaInicio = moment(selectedInicio).format("YYYY-MM-DD");
if (selectFechaFin && moment(selectFechaFin).isValid()) data.fechaFin = moment(selectFechaFin).format("YYYY-MM-DD");
if (fechaNacimiento && moment(fechaNacimiento).isValid()) data.fechaNacimiento = moment(fechaNacimiento).format("YYYY-MM-DD");
formData.append("data", JSON.stringify(data));
console.log("Datos a actualizar:", formData);
for (const [key, value] of formData.entries()) {
console.log("FormData:", key, value);
}
try {
updateIsLoading(true);
const res = await axiosInstance.put(`/servicio/update`, formData); //, admin.tokenArchivo
imprimirMensaje(res.data.message);
// Agregue esta parte para verificar que funcione
if (cartaAceptacion) {
console.log("Carta Aceptación:", {
name: cartaAceptacion.name,
size: cartaAceptacion.size,
type: cartaAceptacion.type,
});
}
if (cartaTermino) {
console.log("Carta Término:", cartaTermino.name);
}
if (informeGlobal) {
console.log("Informe Global:", informeGlobal.name);
}
const token = localStorage.getItem("token") || "";
// Falta corregir este endpoint utilizar la variable del .env
const res = await axios.put(`${process.env.NEXT_PUBLIC_API_URL}/servicio/update`, formData, {
headers: {
Authorization: `Bearer ${token}`,
},
}); //, admin.tokenArchivo
Swal.fire('Exito', 'Se actualizaron los datos correctamente.', 'success')
// imprimirMensaje(res.data.message);
updateIsLoading(false);
router.push("/administrador/servicio");
router.replace("/administrador/servicio");
} catch (err: unknown) {
Swal.fire('Error', 'No se pudieron actualizar los datos', 'error')
updateIsLoading(false);
if (isAxiosError(err)) imprimirError(err.response?.data ?? err.message);
else if (err instanceof Error) imprimirError(err.message);
else imprimirError(err);
// if (isAxiosError(err)) imprimirError(err.response?.data ?? err.message);
// else if (err instanceof Error) imprimirError(err.message);
// else imprimirError(err);
}
};
@@ -146,17 +203,28 @@ export default function EditarServicio({
const password = async () => {
const data = { servicioid };
console.log("Id del servicio para nueva password", data);
console.log("Id servicio entrando a la info", data.servicioid)
//const idServicio ={ idServicio: servicioid }
try {
console.log("Si entro par mandar la solicitud")
updateIsLoading(true);
const res = await axiosInstance.put(`/usuario/new_password_alumno`, data);
imprimirMensaje(res.data.message);
const res = await axiosInstance.post(`/usuario/new-password-alumno/${data.servicioid}`);
// imprimirMensaje(res.data.message);
Swal.fire('Exito', 'Se actualizo la contraseña', 'success')
updateIsLoading(false);
router.push("/admin");
router.replace("/administrador");
} catch (err: unknown) {
Swal.fire('Error', 'No se pudo mandar la nueva contraseña', 'error')
console.log("No esta mandando la solicitud")
updateIsLoading(false);
if (isAxiosError(err)) imprimirError(err.response?.data || err.message);
else if (err instanceof Error) imprimirError(err.message);
else imprimirError(err);
// if (isAxiosError(err)) imprimirError(err.response?.data || err.message);
// else if (err instanceof Error) imprimirError(err.message);
// else imprimirError(err);
}
};
@@ -190,8 +258,44 @@ export default function EditarServicio({
} else {
setActualizarFechaFin(true);
}
console.log("Esta es la info de servicio", servicio);
}, [fechaInicio]);
const confirmarActualizarDatos = () => {
Swal.fire({
title: '¿Seguro(a) que quiere actualizar estos datos?',
icon: 'warning',
showCancelButton: true,
confirmButtonText: 'Confirmar',
cancelButtonText: 'Cancelar',
confirmButtonColor: "#0d6efd",
cancelButtonColor: "#dc3545",
}).then((result) => {
if (result.isConfirmed) {
actualizar()
}
})
}
const confirmarEnvioPassword = () => {
Swal.fire({
title: '¿Seguro(a) que quieres cambiar/reenviar la contraseña de este alumno?',
icon: 'warning',
showCancelButton: true,
confirmButtonText: 'Confirmar',
cancelButtonText: 'Cancelar',
confirmButtonColor: "#0d6efd",
cancelButtonColor: "#dc3545",
}).then((result) => {
if (result.isConfirmed) {
password();
}
})
}
// Validar tamaños de archivos
useEffect(() => {
if (cartaAceptacion && !sizeFileValido(cartaAceptacion))
@@ -211,7 +315,7 @@ export default function EditarServicio({
useEffect(() => {
const id = Number(localStorage.getItem("idServicio"));
console.log("ID Servicio para editar:", id);
if (!id) router.push("/admin");
if (!id) router.replace("/administrador");
else {
setServicioid(id);
console.log("idServicio seteado:", id);
@@ -223,143 +327,241 @@ export default function EditarServicio({
return (
<div className="space-y-4 mt-6">
<h3 className="text-2xl font-semibold mb-4">
Editar información del alumno
</h3>
<h2 className="fw-bold mb-4">
Editar información del alumno
</h2>
{/* Correo */}
<div>
<label className="block font-medium mb-1">
Correo electrónico del alumno
</label>
<input
type="email"
placeholder={servicio.correo || "Correo"}
value={correo}
onChange={(e) => setCorreo(e.target.value)}
className="border rounded p-2 w-full"
/>
</div>
{/* Fecha inicio */}
<div>
<label className="block font-medium mb-1">Fecha de inicio</label>
<DatePicker
selected={fechaFin}
onChange={(date: Date | null) => {
if (date) setFechaFin(date);
}}
minDate={fechaInicio}
className="border rounded p-2 w-full"
/>
</div>
{/* Fecha fin */}
<div>
<label className="block font-medium mb-1">Fecha de fin</label>
<DatePicker
selected={fechaInicio}
onChange={(date: Date | null) => {
if (date) setFechaInicio(date);
}}
minDate={new Date("2020-01-01")}
className="border rounded p-2 w-full"
/>
</div>
{/* Dirección */}
{servicio.direccion && (
<div>
<label className="block font-medium mb-1">Dirección</label>
<input
type="text"
placeholder={servicio.direccion}
value={direccion}
onChange={(e) => setDireccion(e.target.value)}
className="border rounded p-2 w-full"
/>
{/* Correo */}
<div className="mb-3">
<label className="form-label fw-semibold block font-medium mb-1">
Correo electrónico del alumno
</label>
<input
type="email"
placeholder={servicio.correo || "Correo"}
value={correo}
onChange={(e) => setCorreo(e.target.value)}
className="form-control border rounded p-2 w-full"
/>
</div>
)}
{/* Teléfono */}
{servicio.telefono && (
<div>
<label className="block font-medium mb-1">Teléfono</label>
<input
type="tel"
maxLength={10}
placeholder={servicio.telefono}
value={telefono}
onChange={(e) => setTelefono(e.target.value)}
className="border rounded p-2 w-full"
/>
{/* Fecha inicio */}
<Col>
<FormGroup>
<FormLabel>Fecha de inicio</FormLabel>
<InputGroup>
<InputGroup.Text>
<FaRegCalendar />
</InputGroup.Text>
<DatePicker
selected={selectedInicio}
onChange={(date: Date | null ) => {
if (date) setSelectedInicio(date);
}}
placeholderText="Selecciona una fecha de inicio"
// minDate={fechaInicio} Quitamos la restricción de fecha mínima
dateFormat={'dd-MM-yyyy'}
className="form-control"
wrapperClassName="flex-grow-1"
calendarClassName="mi-calendario"
showMonthDropdown
showYearDropdown
scrollableYearDropdown
yearDropdownItemNumber={60}
dropdownMode="select"
locale='es'
/>
</InputGroup>
</FormGroup>
</Col>
<Col>
<FormGroup>
<FormLabel>Fecha de fin</FormLabel>
<InputGroup>
<InputGroup.Text>
<FaRegCalendar />
</InputGroup.Text>
<DatePicker
selected={selectFechaFin}
onChange={(date: Date | null) => {
if (date) setSelectedFechaFin(date);
}}
placeholderText="Selecciona una fecha de fin"
dateFormat={'dd-MM-yyyy'}
className="form-control"
wrapperClassName="flex-grow-1"
calendarClassName="mi-calendario"
showMonthDropdown
showYearDropdown
scrollableYearDropdown
yearDropdownItemNumber={60}
dropdownMode="select"
locale='es'
/>
</InputGroup>
</FormGroup>
</Col>
{/*
Fecha inicio
<div className="mb-3">
<label className="form-label fw-semibold block font-medium mb-1">Fecha de inicio</label>
<DatePicker
selected={fechaFin}
onChange={(date: Date | null) => {
if (date) setFechaFin(date);
}}
minDate={fechaInicio}
className="border rounded p-2 w-full"
/>
</div>
)}
{/* Fecha nacimiento */}
{servicio.fechaNacimiento && (
<div>
<label className="block font-medium mb-1">
Fecha de nacimiento
</label>
<DatePicker
selected={fechaFin}
onChange={(date: Date | null) => {
if (date) setFechaFin(date);
}}
minDate={fechaInicio}
className="border rounded p-2 w-full"
/>
fecha fin
<div className="mb-3">
<label className="form-labeñ fw-semibold block font-medium mb-1">Fecha de fin</label>
<DatePicker
selected={fechaInicio}
onChange={(date: Date | null) => {
if (date) setFechaInicio(date);
}}
minDate={new Date("2020-01-01")}
className="border rounded p-2 w-full"
/>
</div>
)}
*/}
{/* Subir archivos */}
<div className="space-y-2">
<label className="block font-medium mb-1">Carta de aceptación</label>
<input
type="file"
accept="application/pdf"
onChange={(e) =>
setCartaAceptacion(e.target.files ? e.target.files[0] : null)
}
className="border p-2 rounded w-full"
/>
</div>
{/* Dirección */}
{servicio.direccion && (
<div className="mb-3">
<label className="form-label fw-semibold block font-medium mb-1">Dirección</label>
<input
type="text"
placeholder={servicio.direccion}
value={direccion}
onChange={(e) => setDireccion(e.target.value)}
className="form-control border rounded p-2 w-full"
/>
</div>
)}
{/* Botones */}
<div className="mt-6 flex gap-4">
<button
disabled={mostrar()}
onClick={() =>
imprimirWarning(
"¿Seguro(a) que quiere actualizar estos datos?",
actualizar
)
}
className={`px-4 py-2 rounded text-white ${
mostrar()
? "bg-gray-400 cursor-not-allowed"
: "bg-blue-600 hover:bg-blue-700"
}`}
>
Actualizar Datos
</button>
{/* Teléfono */}
{servicio.telefono && (
<div className="mb-3">
<label className="form-label fw-semibold block font-medium mb-1">Teléfono</label>
<input
type="tel"
maxLength={10}
placeholder={servicio.telefono}
value={telefono}
onChange={(e) => setTelefono(e.target.value)}
className="form-control border rounded p-2 w-full"
/>
</div>
)}
<button
disabled={
servicio.Status.idStatus === 1 || servicio.Status.idStatus === 7
}
onClick={() =>
imprimirWarning(
"¿Seguro(a) que quieres cambiar/reenviar la contraseña de este alumno?",
password
)
}
className="px-4 py-2 rounded bg-blue-500 hover:bg-blue-600 text-white"
>
Cambiar/Reenviar contraseña
</button>
</div>
{/*
{servicio.fechaNacimiento && (
<Col>
<FormGroup>
<FormLabel>Fecha de nacimiento</FormLabel>
<InputGroup>
<InputGroup.Text>
</InputGroup.Text>
</InputGroup>
</FormGroup>
</Col>
)}
{servicio.fechaNacimiento && (
<div className="mb-3">
<label className="form-label fw-semibold block font-medium mb-1">
Fecha de nacimiento
</label>
<DatePicker
selected={fechaFin}
onChange={(date: Date | null) => {
if (date) setFechaNacimiento(date);
}}
minDate={fechaInicio}
className="border rounded p-2 w-full"
/>
</div>
)}
*/}
<FormGroup className="mb-4">
<FormLabel>Carta de aceptaciòn</FormLabel>
<div
className="border p-4 text-center rounded"
style={{ cursor: "pointer" }}
onClick={() => document.getElementById("pdfInput")?.click()}
>
<FaUpload size={40} className="mb-2"/>
<p className="mb-1">
{cartaAceptacion?.name || 'Arrastra aqui tu archivo o da click aqui para buscar'}
</p>
<p className="is-size-6">Tamaño màximo 20MB</p>
<p className="is-size-7">Si al momento de elegir un archivo este no se selecciona, haga click en cancelar en la ventana emergente e intente de nuevo.</p>
</div>
<input
id="pdfInput"
type="file"
accept="application/pdf"
style={{ display: 'none'}}
onChange={(e) => {
setCartaAceptacion(e.target.files ? e.target.files[0] : null)
}}
/>
</FormGroup>
{/* Subir archivos */}
{/*
<div className="space-y-2">
<label className="block font-medium mb-1">Carta de aceptación</label>
<input
type="file"
accept="application/pdf"
onChange={(e) =>
setCartaAceptacion(e.target.files ? e.target.files[0] : null)
}
className="border p-2 rounded w-full"
/>
</div>
*/}
{/* Botones */}
<div className="mt-6 flex gap-4 mb-5">
<button
disabled={mostrar()}
onClick={confirmarActualizarDatos}
className={`me-2 px-4 py-2 rounded text-white ${
mostrar()
? "bg-gray-400 cursor-not-allowed"
: "bg-blue-600 hover:bg-blue-700"
}`}
>
Actualizar Datos
</button>
<button
disabled={
servicio.status.idStatus === 1 || servicio.status.idStatus === 7
}
onClick={confirmarEnvioPassword}
className="px-4 py-2 rounded bg-blue-500 hover:bg-blue-600 text-white"
>
Cambiar/Reenviar contraseña
</button>
</div>
</div>
);
}
@@ -3,6 +3,7 @@ import { useState } from "react";
import { Col, FormGroup, FormLabel, FormSelect, InputGroup, Button } from "react-bootstrap";
import { axiosInstance } from "@/api/config"; // tu config de axios
import { isAxiosError } from 'axios';
import Swal from "sweetalert2";
//import fileDownload from "js-file-download";
interface Props {
@@ -33,15 +34,47 @@ export default function GustavoBazPrada({
}
);
// Crear blob manualmente
const blob = new Blob([res.data], { type: "text/csv" });
// Crear URL temporal para descargar
const url = window.URL.createObjectURL(blob);
// Crear link "virtual"
const link = document.createElement("a");
link.href = url;
link.download = `${selectedYear}_gustavo_baz_prada.csv`;
// Disparar descarga
document.body.appendChild(link);
link.click();
// limpiar URL temporal
link.remove();
window.URL.revokeObjectURL(url);
Swal.fire({
title: 'Éxito',
text: 'Se descargo el archivo correctamente.',
icon: 'success',
})
//fileDownload(res.data, `${selectedYear}_gustavo_baz_prada.csv`);
setSelectedYear("");
updateIsLoading?.(false);
} catch (err: unknown) {
Swal.fire({
title: 'Error',
text: 'No se pudo completar la descarga.',
icon: 'error',
})
updateIsLoading?.(false);
if (isAxiosError(err)) imprimirError?.(String(err.response?.data) || err.message || 'Error al descargar');
else if (err instanceof Error) imprimirError?.(err.message);
else imprimirError?.('Error al descargar');
// if (isAxiosError(err)) imprimirError?.(String(err.response?.data) || err.message || 'Error al descargar');
// else if (err instanceof Error) imprimirError?.(err.message);
// else imprimirError?.('Error al descargar');
}
};
@@ -51,20 +84,20 @@ export default function GustavoBazPrada({
<Col>
<FormGroup>
<FormLabel>Selecciona un año:</FormLabel>
<InputGroup>
<FormSelect
value={selectedYear}
onChange={(e) => setSelectedYear(e.target.value)}
>
<option value="">Seleccione un año:</option>
{(years || []).map((y, i) => (
<option key={i} value={y}>
{y}
</option>
))}
</FormSelect>
</InputGroup>
<FormLabel>Selecciona un año:</FormLabel>
<InputGroup>
<FormSelect
value={selectedYear}
onChange={(e) => setSelectedYear(e.target.value)}
>
<option value="">Seleccione un año:</option>
{(years || []).map((y, i) => (
<option key={i} value={y}>
{y}
</option>
))}
</FormSelect>
</InputGroup>
</FormGroup>
</Col>
@@ -43,10 +43,17 @@ export default function InformacionResponsable({ admin, imprimirError, updateIsL
const obtenerResponsable = async () => {
try {
updateIsLoading(true);
const res = await axiosInstance.get(`/usuario/responsable?idUsuario=${idResponsable}`, {
console.log("Este es el id del responsable", idResponsable)
const res = await axiosInstance.get(`/usuario/responsable/${idResponsable}`, {
headers: { Authorization: admin.token },
});
setResponsable(res.data);
console.log("Esta es la info de la res", res.data)
console.log("Esta es la info con doble data", res.data.data)
obtenerProgramas();
} catch (err: unknown) {
if (isAxiosError(err)) imprimirError(String(err.response?.data) || err.message || 'Error al obtener responsable');
@@ -60,21 +67,26 @@ export default function InformacionResponsable({ admin, imprimirError, updateIsL
// Funcion para obtener programas
const obtenerProgramas = async () => {
try {
const res = await axiosInstance.get(`/programa/programas_admin?idUsuario=${idResponsable}`, {
headers: { Authorization: admin.token },
});
setProgramas(res.data);
const res = await axiosInstance.get(`/programa/programas_admin/${idResponsable}`,);
console.log("Esta es la info de la res de programas", res.data)
console.log("Esta es la info con doble data de programas", res.data.data)
setProgramas(res.data.data);
} catch (err: unknown) {
if (isAxiosError(err)) imprimirError(String(err.response?.data) || err.message || 'Error al obtener programas');
else if (err instanceof Error) imprimirError(err.message);
else imprimirError('Error al obtener programas');
if (isAxiosError(err)) {
console.error("ERROR COMPLETO:", err.response?.data);
imprimirError(JSON.stringify(err.response?.data, null, 2));
} else if (err instanceof Error) {
imprimirError(err.message);
} else imprimirError('Error al obtener programas');
}
};
// Cargar al montar
useEffect(() => {
const id = Number(localStorage.getItem('idResponsable'));
if (!id) {
router.push('/administrador/responsables');
} else {
@@ -109,18 +121,18 @@ export default function InformacionResponsable({ admin, imprimirError, updateIsL
<div className="mb-3">
<label className="form-label fw-semibold">Clave de programa:</label>
<select
className="form-select"
value={programa.clavePrograma || ''}
onChange={(e) =>
setPrograma(programas.find((p) => p.clavePrograma === e.target.value) || {})
}
className="form-select"
value={programa.clavePrograma || ''}
onChange={(e) =>
setPrograma(programas.find((p) => p.clavePrograma === e.target.value) || {})
}
>
<option value="">Elija una clave de programa</option>
{programas.map((p, i) => (
<option key={i} value={p.clavePrograma}>
{p.clavePrograma}
</option>
))}
{programas.map((p, i) => (
<option key={i} value={p.clavePrograma}>
{p.clavePrograma}
</option>
))}
</select>
</div>
@@ -5,6 +5,7 @@ import moment from "moment";
import "moment/locale/es";
import { Button, Row, Col, Form } from "react-bootstrap";
import { useRouter } from "next/navigation";
import Link from "next/link";
interface Usuario {
usuario?: string;
@@ -21,9 +22,10 @@ interface Status {
}
interface Alumno {
Usuario?: Usuario;
Carrera?: Carrera;
Status?: Status;
idCasoEspecial: number;
usuario?: Usuario;
carrera?: Carrera;
status?: Status;
creditos?: number;
correo?: string;
fechaNacimiento?: string;
@@ -56,11 +58,100 @@ export default function InformacionCasoEspecial({ alumno }: Props) {
return (
<div className="mt-4">
{/* Título */}
<h3 className="text-primary fw-bold mb-3">{alumno.Status?.status}</h3>
<h2 className="fw-bold mb-3">{alumno.status?.status}</h2>
<h4 className="mb-4">Datos personales</h4>
<div className="mb-3">
<label className="form-label fw-semibold">Número de Cuenta: </label>
<p className="form-control">{alumno.usuario?.usuario || ''}</p>
</div>
<div className="fw-semibold mb-3">
<label className="form-label">Nombre:</label>
<p className="form-control">{alumno.usuario?.nombre || ''}</p>
</div>
<div className="fw-semibold mb-3">
<label className="form-label">Carrera:</label>
<p className="form-control">{alumno.carrera?.carrera || ''}</p>
</div>
<div className="fw-semibold mb-3">
<label className="form-label">Créditos:</label>
<p className="form-control">{alumno.creditos || ''}</p>
</div>
<div className="fw-semibold mb-3">
<label className="form-label">Correo:</label>
<p className="form-control">{alumno.correo || ''}</p>
</div>
<div className="fw-semibold mb-3">
<label className="form-label">Fecha de Nacimiento:</label>
<p className="form-control">{fecha(alumno.fechaNacimiento)}</p>
</div>
<div className="fw-semibold mb-3">
<label className="form-label">Direcciòn:</label>
<p className="form-control">{alumno.direccion}</p>
</div>
<div className="fw-semibold mb-3">
<label className="form-label">Telèfono:</label>
<p className="form-control">{alumno.telefono}</p>
</div>
{alumno.motivo && (
<div className="fw-semibold mb-3">
<label className="form-label">Motivo:</label>
<p className="form-control">{
alumno.motivo === "1"
? "Tercera edad"
: alumno.motivo === "2"
? "Capacidades diferentes"
: ""
}</p>
</div>
)}
{alumno.institucion && (
<div className="fw-semibold mb-3">
<label className="form-label">Instituciòn:</label>
<p className="form-control">{alumno.institucion}</p>
</div>
)}
{alumno.dependencia && (
<div className="fw-semibold mb-3">
<label className="form-label">Dependencia:</label>
<p className="form-control">{alumno.dependencia}</p>
</div>
)}
<div className="fw-semibold mb-3">
<label className="form-label">Fecha Inicio:</label>
<p className="form-control">{fecha(alumno.fechaInicio)}</p>
</div>
<div className="fw-semibold mb-3">
<label className="form-label">Fecha Fin:</label>
<p className="form-control">{fecha(alumno.fechaFin)}</p>
</div>
<div className="fw-semibold mb-5">
<label className="form-label">Fecha Registro:</label>
<p className="form-control">{fecha(alumno.createdAt)}</p>
</div>
{/* Campos */}
{/*
// Informacino de antes
<Form.Group as={Row} className="mb-2">
<Form.Label column sm={3}>Número de Cuenta:</Form.Label>
<Col sm={9}>
@@ -117,7 +208,7 @@ export default function InformacionCasoEspecial({ alumno }: Props) {
</Col>
</Form.Group>
{alumno.motivo && (
{alumno.motivo && (
<Form.Group as={Row} className="mb-2">
<Form.Label column sm={3}>Motivo:</Form.Label>
<Col sm={9}>
@@ -174,15 +265,17 @@ export default function InformacionCasoEspecial({ alumno }: Props) {
<Form.Control plaintext readOnly value={fecha(alumno.createdAt)} />
</Col>
</Form.Group>
*/}
{(alumno.Status?.idStatus === 11 || alumno.Status?.idStatus === 12) && (
<div className="text-end mt-4">
<Button
variant="info"
onClick={() => router.push("/admin/casos_especiales/caso_especial/editar")}
{(alumno.status?.idStatus === 11 || alumno.status?.idStatus === 12) && (
<div className="mt-4 mb-4">
<Link
// variant="primary"
href={`/administrador/casos_especiales/caso_especial/editar/${alumno.idCasoEspecial}`}
className="btn btn-outline-primary is-info is-light"
>
Editar información
</Button>
<span>Editar información</span>
</Link>
</div>
)}
</div>
@@ -1,7 +1,7 @@
import { axiosInstance } from "@/api/config";
import { useRouter } from "next/navigation";
import { useEffect, useState } from "react";
interface Admin {
token?: string;
}
@@ -29,15 +29,15 @@ interface Programa {
programa?: string;
clavePrograma?: string;
acatlan?: boolean;
Usuario?: Usuario;
usuario?: Usuario;
}
interface Datos {
idServicio?: number;
Programa?: Programa;
Carrera?: Carrera;
Usuario?: Usuario;
Status?: Status;
programa?: Programa;
carrera?: Carrera;
usuario?: Usuario;
status?: Status;
creditos?: string;
correo?: string;
createdAt?: string;
@@ -47,6 +47,7 @@ interface Datos {
fechaLiberacion?: string;
telefono?: string;
direccion?: string;
responsableIntermo?: string;
/*
cartaAceptacion?: boolean;
cartaTermino?: boolean;
@@ -75,7 +76,7 @@ export default function InformacionServicio({ admin, imprimirError, updateIsLoad
const [info, setInfo] = useState<Usuario[]>([]);
const [loading, setLoading] = useState(true);
const [programa, setPrograma] = useState<Datos>({});
// const [programa, setPrograma] = useState<Datos>({});
const router = useRouter();
@@ -97,11 +98,11 @@ export default function InformacionServicio({ admin, imprimirError, updateIsLoad
}, []);
*/
useEffect(() => {
console.log('✅ Datos cargados correctamente:', datos);
setPrograma(datos);
console.log('Programa establecido en InformacionServicio:', programa);
}, [datos]);
// useEffect(() => {
// console.log('✅ Datos cargados correctamente:', datos);
// // setPrograma(datos);
// // console.log('Programa establecido en InformacionServicio:', programa);
// }, [datos]);
// Funcion para editar la informacion del servicio
@@ -132,36 +133,41 @@ export default function InformacionServicio({ admin, imprimirError, updateIsLoad
return (
<div className="container mv-5">
<div className="mb-5">
<h3 className="fw-bold mb-4">Datos del programa</h3>
<h3 className="mt-3 mb-2">Datos del programa</h3>
<div className="mb-3">
<label className="form-label fw-semibold">Institucion:</label>
<p className="form-control">{datos.Programa?.institucion || '-'}</p>
<p className="form-control">{datos.programa?.institucion || '-'}</p>
</div>
<div className="mb-3">
<label className="form-label fw-semibold">Dependencias:</label>
<p className="form-control">{datos.Programa?.dependencia || '-'}</p>
<p className="form-control">{datos.programa?.dependencia || '-'}</p>
</div>
<div className="mb-3">
<label className="form-label fw-semibold">Programa:</label>
<p className="form-control">{datos.Programa?.programa}</p>
<label className="form-label fw-semibold">Programa interno:</label>
<p className="form-control">{datos.programa?.programa}</p>
</div>
<div className="mb-3">
<label className="form-label fw-semibold">Clave de programa:</label>
<p className="form-control">{datos.Programa?.clavePrograma}</p>
<p className="form-control">{datos.programa?.clavePrograma}</p>
</div>
<div className="mb-3">
<label className="form-label fw-semibold">Responsable:</label>
<p className="form-control">{datos.Programa?.Usuario?.nombre}</p>
<p className="form-control">{datos.programa?.usuario?.nombre}</p>
</div>
<div className="mb-3">
<label className="form-label fw-semibold">Profesor:</label>
<p className="form-control">{datos?.responsableIntermo || '-'}</p>
</div>
<div className="mb-3">
<label className="form-label fw-semibold">Correo:</label>
<p className="form-control">{datos.Programa?.Usuario?.usuario}</p>
<p className="form-control">{datos.programa?.usuario?.usuario}</p>
</div>
</div>
@@ -170,74 +176,116 @@ export default function InformacionServicio({ admin, imprimirError, updateIsLoad
<div className="mb-3">
<label className="form-label fw-semibold">Numero de cuenta:</label>
<p className="form-control">{datos.Usuario?.usuario || '-'}</p>
<p className="form-control">{datos.usuario?.usuario || '-'}</p>
</div>
<div className="mb-3">
<label className="form-label fw-semibold">Nombre:</label>
<p className="form-control">{datos.Usuario?.nombre || '-'}</p>
<p className="form-control">{datos.usuario?.nombre || '-'}</p>
</div>
<div className="mb-3">
<label className="form-label fw-semibold">Fecha de nacimiento:
<p className="form-control">{datos.fechaNacimiento}</p>
</label>
{/*
<Col>
<FormGroup>
<FormLabel>Seleccione una fecha de fin:</FormLabel>
<InputGroup>
<InputGroup.Text>
<FaRegCalendarAlt />
</InputGroup.Text>
<DatePicker
selected={selectedFin}
onChange={(date) => setSelectedFin(date)}
minDate={minDate}
maxDate={maxDate}
placeholderText="Seleccione una fecha de fin"
dateFormat="yyyy-MM-dd"
className="form-control"
wrapperClassName="flex-grow-1"
calendarClassName="mi-calendario"
/>
</InputGroup>
</FormGroup>
</Col>
<div className="mb-3">
<label className="form-label fw-semibold">Fecha de termino:</label>
<p className="form-control">{formatDate(datos.fechaFin) || '-'}</p>
</div>
*/}
{datos.fechaNacimiento && (
<div className="mb-3">
<label className="form-label fw-semibold">Fecha de nacimiento:</label>
<p className="form-control">{formatDate(datos.fechaNacimiento)}</p>
</div>
)}
<div className="mb-3">
<label className="form-label fw-semibold">Carrera:</label>
<p className="form-control">{datos.Carrera?.carrera || '-'}</p>
<p className="form-control">{datos.carrera?.carrera || '-'}</p>
</div>
<div className="mb-3">
<label className="form-label fw-semibold">Creditos:</label>
<p className="form-control">{datos.creditos || '-'}</p>
<p className="form-control">{datos.creditos?.replace(/^0+/, '') + '%' || '-'}</p>
</div>
<div className="mb-3">
<label className="form-label fw-semibold">Telèfono:</label>
<p className="form-control">{datos.telefono}</p>
</div>
{datos.telefono && (
<div className="mb-3">
<label className="form-label fw-semibold">Telèfono:</label>
<p className="form-control">{datos.telefono}</p>
</div>
)}
{datos.direccion && (
<div className="mb-3">
<label className="form-label fw-semibold">Direcciòn:</label>
<p className="form-control">{datos.direccion}</p>
</div>
)}
<div className="mb-3">
<label className="form-label fw-semibold">Direcciòn:</label>
<p className="form-control">{datos.direccion}</p>
</div>
<div className="mb-3">
<label className="form-label fw-semibold">Email:</label>
<p className="form-control">{datos.correo || '-'}</p>
</div>
<div className="mb-3">
<label className="form-label fw-semibold">Programa Interno:</label>
<p className="form-control">{datos.programaInterno}</p>
</div>
{datos.programaInterno && (
<div className="mb-3">
<label className="form-label fw-semibold">Programa Interno:</label>
<p className="form-control">{datos.programaInterno}</p>
</div>
)}
<div className="mb-3">
<label className="form-label fw-semibold">Profesor:</label>
<p className="form-control">{datos.profesor}</p>
</div>
{datos.profesor && (
<div className="mb-3">
<label className="form-label fw-semibold">Profesor:</label>
<p className="form-control">{datos.profesor}</p>
</div>
)}
<div className="mb-3">
<label className="form-label fw-semibold">Fecha de registro:</label>
<p className="form-control">{datos.createdAt || '-'}</p>
<p className="form-control">{formatDate(datos.createdAt) || '-'}</p>
</div>
<div className="mb-3">
<label className="form-label fw-semibold">fecha de inicio:</label>
<p className="form-control">{datos.fechaInicio || '-'}</p>
<p className="form-control">{formatDate(datos.fechaInicio)|| '-'}</p>
</div>
<div className="mb-3">
<label className="form-label fw-semibold">Fecha de termino:</label>
<p className="form-control">{datos.fechaFin || '-'}</p>
<p className="form-control">{formatDate(datos.fechaFin) || '-'}</p>
</div>
<div className="mb-3">
<label className="form-label fw-semibold">Fecha de liberaciòn:</label>
<p className="form-control">{datos.fechaLiberacion}</p>
</div>
{datos.fechaLiberacion && (
<div className="mb-3">
<label className="form-label fw-semibold">Fecha de liberaciòn:</label>
<p className="form-control">{formatDate(datos.fechaLiberacion)}</p>
</div>
)}
</div>
<button className="btn btn-primary"
@@ -247,4 +295,23 @@ export default function InformacionServicio({ admin, imprimirError, updateIsLoad
</button>
</div>
);
}
// Para dar formato a la fecha
function formatDate(value?: string | null): string {
if (!value) return "";
// Si ya viene como fecha ISO, forzamos a hora local sin modificar el día
const d = new Date(value.includes("T") ? value : `${value}T00:00:00`);
if (isNaN(d.getTime())) {
// console.warn("⚠️ Fecha inválida:", value);
return "";
}
const day = String(d.getDate()).padStart(2, "0");
const month = String(d.getMonth() + 1).padStart(2, "0");
const year = d.getFullYear();
return `${day}/${month}/${year}`;
}
@@ -26,24 +26,24 @@ export default function LiberarCasoEspecial({
const data = { idCasoEspecial };
try {
updateIsLoading(true);
const res = await axiosInstance.put(`/caso_especial/liberacion`, data, { headers: { Authorization: `Bearer ${admin.token}` } });
imprimirMensaje(res.data.message);
router.push('/admin/casos_especiales');
updateIsLoading(true);
const res = await axiosInstance.put(`/caso-especial/liberaciony/${idCasoEspecial}`, data);
imprimirMensaje(res.data.message);
router.replace('/administrador/casos_especiales');
} catch (err: unknown) {
let msg: unknown = 'Error';
if (typeof err === 'object' && err !== null && 'response' in err) {
const anyErr = err as { response?: { data?: unknown } };
if (anyErr.response?.data) msg = anyErr.response.data;
} else if (err instanceof Error) msg = err.message;
imprimirError(msg);
let msg: unknown = 'Error';
if (typeof err === 'object' && err !== null && 'response' in err) {
const anyErr = err as { response?: { data?: unknown } };
if (anyErr.response?.data) msg = anyErr.response.data;
} else if (err instanceof Error) msg = err.message;
imprimirError(msg);
} finally {
updateIsLoading(false);
}
};
return (
<div className="mt-2">
<div className="mt-2 mb-4">
<button
className="bg-green-500 text-white px-4 py-2 rounded hover:bg-green-600"
onClick={() =>
@@ -4,6 +4,7 @@ import { useState } from "react";
import { axiosInstance } from "@/api/config";
import { isAxiosError } from 'axios';
import { useRouter } from "next/navigation";
import Swal from "sweetalert2";
interface Props {
//admin: { token: { headers: { token: string } } };
@@ -35,30 +36,54 @@ export default function ReasignacionProgramas({
);
};
const confirmarActualizacion = () => {
Swal.fire({
title: `¿Seguro(a) que quiere eliminar a ${correoOtroResponsable} y pasar todos los programas a ${responsable.usuario}?`,
icon: "warning",
showCancelButton: true,
confirmButtonText: "Actualizar",
cancelButtonText: "Cancelar",
confirmButtonColor: "#0d6efd",
cancelButtonColor: "#dc3545",
}).then((result) => {
if (result.isConfirmed) {
reasignarProgramas(); // 👈 aquí ejecutas la función
}
});
};
// Función principal: reasignar programas
const reasignarProgramas = async () => {
const data = {
idUsuario: responsable.idUsuario,
correoOtroResponsable,
//idUsuario: responsable.idUsuario,
correo: correoOtroResponsable,
};
console.log("Esta es la data que se pasa en el update de responsable", data)
console.log("Esta es la info del correo entrando en el data", data.correo)
try {
updateIsLoading(true);
const res = await axiosInstance.put(`/programa/reasignar_programas`, data);
const res = await axiosInstance.put(`/programa/reasignar_programas/${responsable.idUsuario}`, data);
Swal.fire('Exito', 'Se reasignaron los programas con exito', 'success')
updateIsLoading(false);
imprimirMensaje(res.data.message);
router.push("/admin/responsables/responsable");
// imprimirMensaje(res.data.message);
router.replace("/administrador/responsables");
} catch (err: unknown) {
Swal.fire('Error', 'No se pudieron reasignar los programas', 'error')
// console.log("Esta entrando al catch no sale la peticion")
updateIsLoading(false);
if (isAxiosError(err)) imprimirError(err.response?.data || err.message);
else if (err instanceof Error) imprimirError(err.message);
else imprimirError(err);
// if (isAxiosError(err)) imprimirError(err.response?.data || err.message);
// else if (err instanceof Error) imprimirError(err.message);
// else imprimirError(err);
}
};
return (
<div className="mt-6">
<h3 className="text-2xl font-semibold mb-3">Reasignación de programas</h3>
<h3 className="fw-semibold text-2xl font-semibold mb-3">Reasignación de programas</h3>
<div className="text-base mb-4">
<p>
@@ -69,32 +94,27 @@ export default function ReasignacionProgramas({
</div>
<div className="mb-4">
<label className="block text-sm font-medium text-gray-700 mb-1">
<label className="form-label fw-semibold block text-sm font-medium text-gray-700 mb-2">
Usuario/Correo electrónico
</label>
<input
type="email"
placeholder="Usuario"
value={correoOtroResponsable}
onChange={(e) => setCorreoOtroResponsable(e.target.value)}
className="border border-gray-300 rounded-lg p-2 w-full"
type="email"
placeholder="Usuario"
value={correoOtroResponsable}
onChange={(e) => setCorreoOtroResponsable(e.target.value)}
className="form-control border border-gray-300 rounded-lg p-2 w-full"
/>
</div>
<div className="pt-5">
<div className="pt-3">
<button
className={`px-4 py-2 rounded-md text-white ${
className={`px-4 py-2 rounded-1 text-white ${
mostrarBoton()
? "bg-gray-400 cursor-not-allowed"
: "bg-blue-600 hover:bg-blue-700"
}`}
disabled={mostrarBoton()}
onClick={() =>
imprimirWarning(
`¿Seguro(a) que quiere eliminar a ${correoOtroResponsable} y pasar todos los programas a ${responsable.usuario}?`,
reasignarProgramas
)
}
onClick={confirmarActualizacion}
>
Reasignar
</button>
@@ -0,0 +1,87 @@
import { axiosInstance } from "@/api/config";
import moment from "moment";
import { title } from "process";
import { useState } from "react";
import { Button, Col, Form, InputGroup } from "react-bootstrap";
import { FaRegCalendar } from "react-icons/fa6";
import Swal from "sweetalert2";
export default function ReporteCasoEspecial() {
const [selectedInicio, setSelectedInicio] = useState<Date | null>(null);
const [selectedFin, setSelectedFin] = useState<Date | null>(null);
const descargar = async () => {
console.log("Descargando reporte de caso especial...");
try {
const res = await axiosInstance.get("/caso-especial/reporte", {
params: {
inicio: moment(selectedInicio).format("YYYY-MM-DD"), // Aquí puedes agregar los parámetros necesarios para el reporte de caso especial
fin: moment(selectedFin).format("YYYY-MM-DD"),
// Aquí puedes agregar los parámetros necesarios para el reporte de caso especial
},
responseType: "blob",
});
const blob = new Blob([res.data], { type: "text/csv" });
const url = window.URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
link.download = "reporte_caso_especial.csv";
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
Swal.fire({
title: "Reporte de caso especial descargado",
text: "El reporte de caso especial se ha descargado correctamente.",
icon: "success",
});
setSelectedInicio(null);
setSelectedFin(null);
} catch {
Swal.fire({
title: "Error al descargar el reporte de caso especial",
text: "Hubo un error al descargar el reporte de caso especial. Por favor, inténtalo de nuevo.",
icon: "error",
});
// console.error("Error al descargar el reporte de caso especial.");
}
}
return (
<div className="columns">
<h4 className="mb-3">Reporte Caso Especial</h4>
<section>
<Col>
<Form.Group>
<Form.Label>Seleccione una fecha de inicio:</Form.Label>
<InputGroup.Text>
<FaRegCalendar />
<Form.Control type="date" placeholder="Seleccione una fecha de inicio" value={selectedInicio ? moment(selectedInicio).format("YYYY-MM-DD") : ""} onChange={(e) => setSelectedInicio(e.target.value ? new Date(e.target.value) : null)} />
</InputGroup.Text>
</Form.Group>
</Col>
<Col>
<Form.Group>
<Form.Label>Seleccione una fecha de fin:</Form.Label>
<InputGroup.Text>
<FaRegCalendar />
<Form.Control type="date" placeholder="Seleccione una fecha de fin" value={selectedFin ? moment(selectedFin).format("YYYY-MM-DD") : ""} onChange={(e) => setSelectedFin(e.target.value ? new Date(e.target.value) : null)} />
</InputGroup.Text>
</Form.Group>
</Col>
</section>
<div className="mt-4 mb-3">
<Button onClick={descargar} disabled={false}>Descargar Reporte</Button>
</div>
</div>
)
}
+66 -9
View File
@@ -1,10 +1,15 @@
"use client";
import { useState } from "react";
import { axiosInstance } from "@/api/config";
import { Button, Col, FormGroup, FormLabel, InputGroup, Row } from "react-bootstrap";
import { Button, Col, FormGroup, FormLabel, InputGroup } from "react-bootstrap";
import DatePicker from "react-datepicker";
import moment from "moment";
import { FaCalendarAlt, FaRegCalendarAlt } from "react-icons/fa";
import { FaRegCalendar } from "react-icons/fa6";
import Swal from "sweetalert2";
import { registerLocale } from "react-datepicker";
import { es } from "date-fns/locale/es";
registerLocale("es", es);
//import { saveAs } from "file-saver";
interface Props {
@@ -32,17 +37,44 @@ export default function Reporte({ admin }: Props) {
try {
//updateIsLoading(true);
console.log("Fecha inicio seleccionada:", selectedInicio);
console.log("Fecha fin seleccionada:", selectedFin);
//console.log("Fechas parseadas", selectedInicio ? moment(selectedInicio).format("YYYY") : null, selectedFin ? moment(selectedFin).format("YYYY") : null);
const res = await axiosInstance.get("/servicio/reporte", {
params: {
inicio: moment(selectedInicio).format("YYYY-MM-DD"),
fin: moment(selectedFin).format("YYYY-MM-DD"),
},
headers: {
Authorization: `Bearer ${admin.token}`,
inicio: moment(selectedInicio).format("YYYY-MM-DD"), // Mandamos solo el año, quitamos el mes y día
fin: moment(selectedFin).format("YYYY-MM-DD"), // Mandamos solo el año, quitamos el mes y día
},
responseType: "blob",
});
// Creamos el blob manualmente
const blob = new Blob([res.data], {type: 'text/csv'})
// Creamos la url temporal para la descarga
const url = window.URL.createObjectURL(blob)
// Creamos el link
const link = document.createElement("a")
link.href = url;
link.download = `Reporte.csv`
// Disparar descarga
document.body.appendChild(link)
link.click()
// Limpiamos la url
link.remove()
window.URL.revokeObjectURL(url)
Swal.fire({
title: 'Éxito',
text: 'Se descargo el reporte exitosamente.',
icon: 'success',
})
//saveAs(res.data, "reporte.csv");
// reset
@@ -50,9 +82,18 @@ export default function Reporte({ admin }: Props) {
setSelectedFin(null);
//updateIsLoading(false);
} catch (err: unknown) {
Swal.fire({
title: 'Error',
text: 'No se pudo descargar el reporte.',
icon: 'error',
})
console.log("Error al descargar el reporte", err)
//updateIsLoading(false);
// optional: handle error
}
};
return (
@@ -65,7 +106,7 @@ export default function Reporte({ admin }: Props) {
<FormLabel>Seleccione una fecha de inicio:</FormLabel>
<InputGroup>
<InputGroup.Text>
<FaRegCalendarAlt />
<FaRegCalendar />
</InputGroup.Text>
<DatePicker
selected={selectedInicio}
@@ -77,6 +118,14 @@ export default function Reporte({ admin }: Props) {
className="form-control"
wrapperClassName="flex-grow-1"
calendarClassName="mi-calendario"
showMonthDropdown
showYearDropdown
scrollableYearDropdown
yearDropdownItemNumber={15}
dropdownMode="select"
locale='es'
/>
</InputGroup>
</FormGroup>
@@ -87,7 +136,7 @@ export default function Reporte({ admin }: Props) {
<FormLabel>Seleccione una fecha de fin:</FormLabel>
<InputGroup>
<InputGroup.Text>
<FaRegCalendarAlt />
<FaRegCalendar />
</InputGroup.Text>
<DatePicker
selected={selectedFin}
@@ -99,6 +148,14 @@ export default function Reporte({ admin }: Props) {
className="form-control"
wrapperClassName="flex-grow-1"
calendarClassName="mi-calendario"
showMonthDropdown
showYearDropdown
scrollableYearDropdown
yearDropdownItemNumber={15}
dropdownMode="select"
locale='es'
/>
</InputGroup>
</FormGroup>
@@ -7,7 +7,9 @@ import type { AxiosResponse } from 'axios';
import { Button, Col, Form, FormGroup, FormLabel, FormSelect, InputGroup, Row, Spinner } from "react-bootstrap";
import ServicioSocialTabla from "../servicio-social-tabla";
import { ServicioSocialResponse } from "@/types/responses";
import { FaInfoCircle, FaSchool, FaUser } from "react-icons/fa";
import { FaCircleInfo, FaUser } from "react-icons/fa6";
import { useRouter } from "next/navigation";
import Image from "next/image";
interface Admin {
idTipoUsuario?: number;
@@ -45,6 +47,9 @@ interface CasosEspecialesResponse {
}
export default function TablaCasosEspeciales({ admin, imprimirError }: Props) {
// Para el redirecionamiento
const router = useRouter();
const [isLoading, setIsLoading] = useState(false);
const [page, setPage] = useState(1);
const [total, setTotal] = useState(0);
@@ -88,11 +93,12 @@ export default function TablaCasosEspeciales({ admin, imprimirError }: Props) {
try {
const config = admin?.token ? { headers: { Authorization: `Bearer ${admin.token}` } } : undefined;
console.debug('Obtener servicios - localStorage token:', typeof window !== 'undefined' ? localStorage.getItem('token') : undefined, 'admin.token:', admin?.token);
const res = await axiosInstance.get(`/caso_especial/servicios_especiales?pagina=${pagina}${query}`, config);
const res = await axiosInstance.get(`/caso-especial/servicios_especiales?pagina=${pagina}${query}`);
console.debug('Respuesta casos especiales:', res.data);
setData(res.data.serviciosEspeciales || []);
setData(res.data.data.serviciosEspeciales ?? []);
setTotal(res.data.count);
console.log('Respuesta casos especiales:', res.data);
console.log("Respuesta Caso especial con doble data", res.data.data)
} catch (err: unknown) {
if (imprimirError) {
if (isAxiosError(err)) {
@@ -130,9 +136,12 @@ export default function TablaCasosEspeciales({ admin, imprimirError }: Props) {
useEffect(() => {
console.log("Admin recibido:", admin);
obtenerCatalogoStatus();
// Asegurar que se ejecute la petición si el usuario tiene permisos
if (admin && Number(admin.idTipoUsuario) === 1) {
obtenerCatalogoStatus();
console.log("entro para traer la informacion de los usuarios")
} else if (!admin) {
console.warn("Admin no definido, no se cargaron los casos especiales.");
}
@@ -155,7 +164,7 @@ export default function TablaCasosEspeciales({ admin, imprimirError }: Props) {
<InputGroup>
<InputGroup.Text className="rounded-4">
<FaSchool />
<Image src="/image/numCuenta.svg" width={24} height={24} alt="Icono de escuela" />
</InputGroup.Text>
<Form.Control
@@ -187,7 +196,7 @@ export default function TablaCasosEspeciales({ admin, imprimirError }: Props) {
placeholder="Nombre"
value={search.nombre}
onChange={(e) => setSearch({ ...search, nombre: e.target.value })}
onKeyDown={(e) => e.key === "Enter" && obtenerCasosEspeciales()}
onKeyDown={(e) => e.key === "Enter" && obtenerCasosEspeciales() }
/>
</InputGroup>
</FormGroup>
@@ -199,13 +208,14 @@ export default function TablaCasosEspeciales({ admin, imprimirError }: Props) {
<InputGroup>
<InputGroup.Text className="rounded-4">
<FaInfoCircle />
<FaCircleInfo />
</InputGroup.Text>
<FormSelect
value={search.idStatus}
onChange={(e) => setSearch(prev => ({ ...prev, idStatus: e.target.value }))}
className="rounded-4"
onKeyDown={(e) => { if (e.key === 'Enter') obtenerCasosEspeciales() }}
>
<option value="">Status</option>
{status.map((s) => (
@@ -244,15 +254,27 @@ export default function TablaCasosEspeciales({ admin, imprimirError }: Props) {
{/* Tabla
<ServicioSocialTabla />
onPageChange={onPageChange}
columnasResponsable={admin?.idTipoUsuario === 1}
*/}
<ServicioSocialTabla
data={data}
total={total}
columnaFechaRegistro={true}
columnasResponsable={false}
idTipoUsuario={admin?.idTipoUsuario}
columnasResponsable={admin?.idTipoUsuario === 1}
onPageChange={onPageChange}
columnaFechaInicio={false}
columnaFechaFin={false}
onRowAction={(row, path) => {
try {
router.push(`/responsable/${path}`)
} catch (error) {
console.error('Error al manejar accion de fila', error)
}
}}
/>
</section>
@@ -5,7 +5,7 @@ import { Responsables } from "@/types/responses";
import { useRouter } from "next/navigation";
import { useEffect, useState } from "react";
import { Button, Col, Row, Table } from "react-bootstrap";
import { FaEnvelope, FaUser } from "react-icons/fa";
import { FaEnvelope, FaUser } from "react-icons/fa6";
export default function TablaResponsables() {
const [info, setInfo] = useState<Responsables[]>([]);
@@ -68,6 +68,7 @@ export default function TablaResponsables() {
className="form-control border-0 rounded-4"
value={correo}
onChange={(e) => setCorreo(e.target.value)}
onKeyDown={(e) => {if (e.key === 'Enter') fetchData(correo, nombre)}}
/>
</div>
</Col>
@@ -80,6 +81,7 @@ export default function TablaResponsables() {
className="form-control border-0 rounded-4"
value={nombre}
onChange={(e) => setNombre(e.target.value)}
onKeyDown={(e) => { if (e.key === 'Enter') fetchData(correo, nombre)}}
/>
</div>
</Col>
@@ -1,14 +1,14 @@
"use client";
import React, { useEffect, useRef, useState } from "react";
import { useEffect, useRef, useState } from "react";
import { useRouter } from 'next/navigation';
import { Form, FormGroup, FormLabel, FormControl, FormSelect, Button, InputGroup, Row, Col } from "react-bootstrap";
import { FaUser, FaSchool, FaInfoCircle } from "react-icons/fa";
import { FaUser, FaCircleInfo } from "react-icons/fa6";
import ServicioSocialTabla from "../servicio-social-tabla";
import { axiosInstance } from "@/api/config";
import { ServicioSocialResponse } from "@/types/responses";
import { Prev } from "react-bootstrap/esm/PageItem";
import { AxiosError } from "axios";
import Image from "next/image";
interface Admin {
idTipoUsuario: number;
@@ -38,6 +38,8 @@ export default function TablaServicioSocial({ admin, imprimirError }: Props) {
if (admin?.idTipoUsuario === 1) {
obtenerCatalogoStatus();
}
console.log("Esta es la info que tiene al guardar en la state", data)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [admin?.idTipoUsuario]);
@@ -70,10 +72,14 @@ export default function TablaServicioSocial({ admin, imprimirError }: Props) {
// asegurar que enviamos Authorization si existe en admin, y loggear token para debug
const config = admin?.token ? { headers: { Authorization: `Bearer ${admin.token}` } } : undefined;
console.debug('Obtener servicios - localStorage token:', typeof window !== 'undefined' ? localStorage.getItem('token') : undefined, 'admin.token:', admin?.token);
const res = await axiosInstance.get(`/servicio/servicios_admin?pagina=${paginaActual}${query}`, config);
const res = await axiosInstance.get(`/servicio/admin?pagina=${paginaActual}${query}`);
console.debug('Respuesta servicios_admin:', res.data);
setData(res.data.serviciosAdmin || []);
//setData(res.data)
//setData(res.data.serviciosAdmin);
setTotal(res.data.count ?? 0);
console.log("Esta es la info", res.data)
} catch (err: unknown) {
// manejar error
console.error('Error obtenerServicios', err);
@@ -98,7 +104,6 @@ export default function TablaServicioSocial({ admin, imprimirError }: Props) {
}
};
return (
<section>
<div className="columns">
@@ -107,12 +112,12 @@ export default function TablaServicioSocial({ admin, imprimirError }: Props) {
<section className="container-fluid my-4">
<Form onSubmit={(e) => { e.preventDefault(); obtenerServicios(1); }}>
<Row className="g-3">
<Col md={3}>
<Col xs={12} sm={6} lg={3}>
<FormGroup>
<FormLabel>Número de Cuenta</FormLabel>
<InputGroup>
<InputGroup.Text className="rounded-4">
<FaSchool />
<Image src="/image/numCuenta.svg" width={24} height={24} alt="Icono de escuela" />
</InputGroup.Text>
<FormControl
type="text"
@@ -127,7 +132,7 @@ export default function TablaServicioSocial({ admin, imprimirError }: Props) {
</FormGroup>
</Col>
<Col md={3}>
<Col xs={12} sm={6} lg={3}>
<FormGroup>
<FormLabel>Nombre</FormLabel>
<InputGroup>
@@ -146,17 +151,19 @@ export default function TablaServicioSocial({ admin, imprimirError }: Props) {
</FormGroup>
</Col>
<Col md={3}>
<Col xs={12} sm={6} lg={3}>
<FormGroup>
<FormLabel>Status</FormLabel>
<InputGroup>
<InputGroup.Text className="rounded-4">
<FaInfoCircle />
<FaCircleInfo />
</InputGroup.Text>
<FormSelect
suppressHydrationWarning
value={search.idStatus}
onChange={(e) => setSearch(Prev => ({ ...Prev, idStatus: e.target.value }))}
className="rounded-4"
onKeyDown={(e) => { if (e.key === 'Enter') obtenerServicios()}}
>
<option value="">Status</option>
{status.slice(0, 10).map((s) => (
@@ -167,7 +174,7 @@ export default function TablaServicioSocial({ admin, imprimirError }: Props) {
</FormGroup>
</Col>
<Col md={3} className="d-flex align-items-end">
<Col xs={12} sm={6} lg={3} className="d-flex align-items-end">
<Button type="submit" className="w-100 rounded-5" disabled={isLoading}>
{isLoading ? 'Buscando...' : 'Buscar'}
</Button>
@@ -186,6 +193,7 @@ export default function TablaServicioSocial({ admin, imprimirError }: Props) {
onPageChange={onPageChange}
idTipoUsuario={admin?.idTipoUsuario}
columnasResponsable={admin?.idTipoUsuario === 1}
columnaFechaRegistro={true}
columnaCuestionario={false}
columnaCartaTermino={false}
columnaCuestionarioCompleto={false}
@@ -14,10 +14,10 @@ export default function TituloStatus({ status }: Props) {
console.log('idStatus en TituloStatus:', idStatus);
return (
<div className="mb-5">
{idStatus === 1 && <h3 className="title">Confirmar el pre-registro del alumno</h3>}
{idStatus === 4 && <h3 className="title">Término</h3>}
{idStatus === 5 && <h3 className="title">Validar el término del alumno</h3>}
<div className="mb-4">
{idStatus === 1 && <h2 className="fw-bold mt-4">Confirmar el pre-registro del alumno</h2>}
{idStatus === 4 && <h2 className="fw-bold mt-4">Término</h2>}
{idStatus === 5 && <h2 className="fw-bold mt-4">Validar el término del alumno</h2>}
</div>
);
}
+59 -56
View File
@@ -1,73 +1,76 @@
"use client";
import React from "react";
import { FaUserPlus, FaUserCheck, FaUser, FaUsers, FaUserClock, FaUserTie } from "react-icons/fa6";
interface Props {
idStatus: number;
}
interface Step {
label: string;
icon: string; // Puedes usar iconos de heroicons o font-awesome
visible: boolean;
type?: "default" | "danger";
}
export default function BarraProgreso({ idStatus }: Props) {
const steps: Step[] = [
{ label: "Pre Registro", icon: "account-plus", visible: idStatus < 6 },
{ label: "Pre Registro Validado", icon: "account-clock", visible: idStatus < 6 },
{ label: "Registro", icon: "account", visible: idStatus < 6 },
{ label: "Pre Termino", icon: "account-details", visible: idStatus < 6 },
{ label: "Termino", icon: "account-clock", visible: idStatus < 6 },
{ label: "Liberacion", icon: "account-check", visible: idStatus < 6 },
{ label: "Carta Aceptación Rechazada", icon: "file", visible: idStatus === 6, type: "danger" },
{ label: "Carta Termino Rechazada", icon: "file", visible: idStatus === 7, type: "danger" },
{ label: "Informe Global Rechazado", icon: "file", visible: idStatus === 8, type: "danger" },
{ label: "Cancelado", icon: "account-cancel", visible: idStatus === 9, type: "danger" },
// Si el status es 7, mostrar como si fuera 1 (Pre Registro)
// Si el status es 8 o 9, mostrar como 5 (Termino)
const statusMapeado = idStatus === 7 ? 1 : (idStatus === 8 || idStatus === 9 ? 5 : idStatus);
const steps = [
{ label: "Pre Registro", icon: <FaUserPlus />, id: 1 },
{ label: "Pre Registro Validado", icon: <FaUserCheck />, id: 2 },
{ label: "Registro", icon: <FaUser />, id: 3 },
{ label: "Pre Termino", icon: <FaUsers />, id: 4 },
{ label: "Termino", icon: <FaUserClock />, id: 5 },
{ label: "Liberacion", icon: <FaUserTie />, id: 6 },
];
return (
<div className="b-steps-container mt-6 mb-5">
<div className="b-steps">
{steps.filter(s => s.visible).map((step, idx) => (
<div key={idx} className={`step-item ${step.type === 'danger' ? 'is-danger' : 'is-info'}`} aria-hidden>
<div className="step-marker">
{/* Placeholder icon: use font-awesome or heroicons in the app */}
<span className={`icon ${step.type === 'danger' ? 'icon-danger' : 'icon-info'}`} aria-hidden>
<i className={`fa fa-${step.icon}`} />
</span>
</div>
<div className="step-details">
<div className="step-title">{step.label}</div>
</div>
</div>
))}
</div>
<div className="container mt-5">
<div className="d-flex justify-content-center align-items-center position-relative">
{steps.map((step, index) => {
const isActive = statusMapeado >= step.id;
const isCurrent = statusMapeado === step.id;
<style jsx>{`
.b-steps { display: flex; flex-wrap: wrap; gap: 0.5rem; align-items: stretch; }
.step-item { display: flex; align-items: center; padding: 0.6rem 0.9rem; border-radius: 6px; background: #e6f2ff; color: #0b66b2; min-width: 220px; }
.step-item.is-info { background: #ecf8ff; color: #0b66b2; }
.step-item.is-danger { background: #ffecec; color: #a10b0b; }
.step-marker { display:flex; align-items:center; justify-content:center; width:40px; height:40px; border-radius:50%; margin-right:0.75rem; background: rgba(255,255,255,0.6); }
.step-item.is-info .step-marker { background: #dff4ff; }
.step-item.is-danger .step-marker { background: #ffdede; }
.icon { font-size: 1.1rem; }
.step-title { font-weight: 600; font-size: 0.95rem; }
return (
<div key={index} className="text-center position-relative flex-fill">
{/* Línea de conexión */}
{index > 0 && (
<div
className={`position-absolute top-50 start-0 translate-middle-y w-100 border-top ${
isActive ? "border-primary" : "border-secondary opacity-25"
}`}
style={{ zIndex: 0 }}
></div>
)}
/* Mimic the Buefy rule to hide ::before/::after on danger steps */
.b-steps .step-item.is-danger::before,
.b-steps .step-item.is-danger::after {
display: none !important;
content: none !important;
}
{/* Círculo del paso */}
<div
className={`rounded-circle d-flex justify-content-center align-items-center mx-auto mb-2 border ${
isActive
? "bg-primary text-white border-primary"
: isCurrent
? "border-primary text-primary bg-white"
: "bg-light text-secondary border-secondary opacity-75"
}`}
style={{
width: "48px",
height: "48px",
position: "relative",
zIndex: 1,
}}
>
{step.icon}
</div>
@media (max-width: 768px) {
.step-item { min-width: 140px; padding: 0.45rem 0.6rem; }
.step-title { font-size: 0.85rem; }
}
`}</style>
{/* Título */}
<div
className={`fw-semibold ${
isActive || isCurrent ? "text-dark" : "text-secondary"
}`}
style={{ fontSize: "0.95rem" }}
>
{step.label}
</div>
</div>
);
})}
</div>
</div>
);
}
@@ -1,12 +1,11 @@
"use client";
import React, { useState } from "react";
import DatePicker from "react-datepicker";
import { useState } from "react";
import "react-datepicker/dist/react-datepicker.css";
import { axiosInstance } from '@/api/config';
import { isAxiosError } from 'axios';
import type { AxiosResponse } from 'axios';
import moment from "moment";
import { useRouter } from "next/navigation";
import Swal from "sweetalert2";
interface Alumno {
token: { headers: Record<string, string> };
@@ -17,7 +16,7 @@ interface Alumno {
interface Props {
idServicio: number;
alumno: Alumno;
imprimirMensaje: (msg: string) => void;
imprimirMensaje: (message: string) => void;
imprimirWarning: (msg: string, onConfirm: () => void) => void;
imprimirError: (err: unknown) => void;
obtenerServicio: () => void;
@@ -28,11 +27,11 @@ interface PreRegistroData {
idServicio: number;
direccion: string;
telefono: string;
fechaNacimiento: string;
// fechaNacimiento: string;
}
interface ApiResponse {
data: { message: string };
message: string ;
}
export default function CompletarDatosPersonales({
@@ -49,89 +48,143 @@ export default function CompletarDatosPersonales({
const [nacimiento, setNacimiento] = useState<Date>(new Date());
const maxDate = new Date();
const router = useRouter();
const confirmarDatos = () => {
Swal.fire({
title: '¿Estas seguro(a) que tus datos son correctos?',
icon: 'warning',
showCancelButton: true,
confirmButtonText: "Sí, actualizar",
cancelButtonText: "Cancelar",
confirmButtonColor: "#0d6efd",
cancelButtonColor: "#dc3545",
}).then((result) => {
if(result.isConfirmed) {
terminarPreRegistro();
}
})
}
const terminarPreRegistro = async () => {
const data: PreRegistroData = {
idServicio,
direccion,
telefono,
fechaNacimiento: moment(nacimiento).format("YYYY-MM-DD"),
//fechaNacimiento: moment(nacimiento).format("YYYY-MM-DD"),
};
try {
updateIsLoading(true);
const res: AxiosResponse<ApiResponse> = await axiosInstance.put(`/servicio/registro_validado`, data, alumno.token);
imprimirMensaje(res.data.data.message);
obtenerServicio();
} catch (err: unknown) {
if (isAxiosError(err)) {
imprimirError(err.response?.data || err.message);
} else {
imprimirError(err);
}
} finally {
console.log("Datos enviados:", data);
const res: AxiosResponse<ApiResponse> = await axiosInstance.post(`/servicio/registro-validado`, data); // Preguntar si no crear el registro por error
updateIsLoading(false);
// imprimirMensaje(res.data.message);
Swal.fire("Exito", "Se actualizaron tus datos correctamente", "success");
router.refresh();
await obtenerServicio();
setTimeout(() => {
router.replace("/"); //Estaria bien corregir esta parte.
}, 1000);
} catch (err) {
Swal.fire("Error", "No se pudieron actualizar tur datos, por favor intentelo en otro momento", "error")
// if (isAxiosError(err)) {
// imprimirError(err.response?.data || err.message);
// } else {
// imprimirError(err);
// }
}
// } finally {
// updateIsLoading(false);
// }
};
return (
<div className="space-y-4">
<h3 className="text-xl font-semibold">Formulario Pre-registro</h3>
<div className="container space-y-4">
<h2 className="text-xl fw-semibold">Formulario Pre-registro</h2>
{/* Fecha de nacimiento */}
<div>
<label className="block mb-1 font-medium">Fecha de nacimiento</label>
{/* <Col>
<FormGroup>
<FormLabel className="form-label fw-semibold">Fecha de nacimiento:</FormLabel>
<InputGroup>
<InputGroup.Text>
<FaRegCalendarAlt />
</InputGroup.Text>
<DatePicker
selected={nacimiento}
onChange={(date: Date | null) => {
if (date) setNacimiento(date);
}}
maxDate={maxDate}
className="border border-gray-300 rounded px-3 py-2 form-control"
placeholderText="Fecha de nacimiento"
wrapperClassName="flex-grow-1"
calendarClassName="mi-calendario"
/>
</InputGroup>
</FormGroup>
</Col> */}
{/*
<div className="mb-3">
<label className="block mb-1 fw-semibold">Fecha de nacimiento</label>
<DatePicker
selected={nacimiento}
onChange={(date: Date | null) => {
if (date) setNacimiento(date);
}}
maxDate={maxDate}
className="border border-gray-300 rounded px-3 py-2 w-full"
className="border border-gray-300 rounded px-3 py-2 form-control"
placeholderText="Fecha de nacimiento"
/>
wrapperClassName="flex-grow-1"
calendarClassName="mi-calendario"
/>
</div>
*/}
{/* Teléfono */}
<div>
<label className="block mb-1 font-medium">Teléfono</label>
<div className="mb-3">
<label className="block mb-1 form-label fw-semibold mb-2">Teléfono</label>
<input
type="tel"
placeholder="Teléfono"
maxLength={10}
value={telefono}
onChange={(e) => setTelefono(e.target.value)}
className="border border-gray-300 rounded px-3 py-2 w-full"
type="tel"
placeholder="Teléfono"
maxLength={10}
value={telefono}
onChange={(e) => setTelefono(e.target.value)}
className="border border-gray-300 rounded px-3 py-2 form-control"
/>
</div>
{/* Dirección */}
<div>
<label className="block mb-1 font-medium">Dirección</label>
<div className="mb-3">
<label className="block mb-1 form-label fw-semibold mb-2">Domicilio</label>
<input
type="text"
placeholder="Dirección"
maxLength={200}
value={direccion}
onChange={(e) => setDireccion(e.target.value)}
className="border border-gray-300 rounded px-3 py-2 w-full"
type="text"
placeholder="Domicilio"
maxLength={200}
value={direccion}
onChange={(e) => setDireccion(e.target.value)}
className="border border-gray-300 rounded px-3 py-2 form-control"
/>
</div>
{/* Botón enviar */}
<div className="text-center mt-4">
<button
disabled={!telefono || !direccion}
onClick={() =>
imprimirWarning(
"¿Estas seguro(a) que tus datos son correctos?",
terminarPreRegistro
)
}
className={`px-4 py-2 rounded text-white ${
!telefono || !direccion
? "bg-gray-400 cursor-not-allowed"
: "bg-green-600 hover:bg-green-700"
}`}
disabled={!telefono || !direccion}
onClick={confirmarDatos}
className={`px-4 py-2 rounded text-white mb-5 ${
!telefono || !direccion
? "bg-gray-400 cursor-not-allowed"
: "bg-green-600 hover:bg-green-700"
}`}
>
Enviar
</button>
@@ -2,6 +2,9 @@
import React, { useEffect, useMemo, useState } from "react";
import { axiosInstance } from "@/api/config";
import { useRouter } from "next/navigation";
import Swal from "sweetalert2";
import { AxiosError } from "axios";
type Pregunta = {
id: string;
@@ -25,9 +28,16 @@ type Tabla = {
type Formulario = { titulo?: string; descripcion?: string; preguntas: Pregunta[]; tablas: Tabla[] };
export default function FullCuestionario() {
interface Props{
idServicio: number;
}
export default function FullCuestionario({ idServicio}: Props) {
const [respuestas, setRespuestas] = useState<Record<string, string | string[] | Record<number, string | null> | null>>({});
const [isLoading, setIsLoading] = useState(false);
const [isFormDisabled, setIsFormDisabled] = useState(false);
const router = useRouter();
const formulario: Formulario = useMemo(
() => ({
@@ -97,6 +107,29 @@ export default function FullCuestionario() {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
// Verificar si el cuestionario ya fue contestado
useEffect(() => {
const checkIfFormAlreadyCompleted = async () => {
try {
const idServicio = typeof window !== 'undefined' ? localStorage.getItem('idServicio') : null;
if (!idServicio) return;
const response = await axiosInstance.get(`/servicio/admin?idServicio=${idServicio}`);
const idCuestionarioAlumno2 = response.data?.idCuestionarioAlumno2;
// Si ya existe un cuestionario contestado, deshabilitar el formulario
if (idCuestionarioAlumno2 && idCuestionarioAlumno2 > 0) {
setIsFormDisabled(true);
window.alert('Este cuestionario ya ha sido contestado. No puedes modificar tus respuestas.');
}
} catch (error) {
console.error('Error al verificar cuestionario:', error);
}
};
checkIfFormAlreadyCompleted();
}, []);
const isMobile = () => typeof window !== 'undefined' && window.innerWidth < 576;
const sortedItems = useMemo(() => {
@@ -242,20 +275,41 @@ export default function FullCuestionario() {
if (!validateResponses()) return;
setIsLoading(true);
try {
const data = { ...formatearRespuestas(respuestas), idServicio: typeof window !== 'undefined' ? localStorage.getItem('idServicio') : null };
await axiosInstance.post(`/cuestionario_alumno`, data);
window.alert('Se enviaron los datos correctamente');
// const idServicioStorage = typeof window !== 'undefined' ? localStorage.getItem('idServicio') : null;
// if (!idServicioStorage) {
// window.alert('No se encontró el ID del servicio. Por favor, vuelve a iniciar sesión.');
// }
// Para verificar las respuestas de las preguntas
console.log('Respuestas a enviar:', respuestas);
console.log('Respuestas formateadas:', formatearRespuestas(respuestas));
console.log('Respouesta pregunta p_12_C_5', formatearRespuestas(respuestas)['p12_C_5']);
const data = { ...formatearRespuestas(respuestas), idServicio };
console.log('Este es el id', data.idServicio)
const res = await axiosInstance.post(`/cuestionario-alumno2`, data);
const mensaje = res?.data?.message || 'Se subio el cuestionario correctamente.';
Swal.fire('Exito', mensaje, 'success')
// window.alert('Se enviaron los datos correctamente');
if (typeof window !== 'undefined') {
localStorage.removeItem('idCuestionarioAlumno');
window.location.href = '/alumno';
// window.location.href = '/alumno';
router.replace('/alumno');
}
} catch (err: unknown) {
let message = 'Error al enviar el formulario';
if (typeof err === 'object' && err !== null && 'response' in err) {
const anyErr = err as { response?: { data?: { message?: unknown } } };
if (anyErr.response?.data?.message) message = String(anyErr.response.data.message);
} else if (err instanceof Error) message = err.message;
window.alert(message);
} catch (err) {
const axiosErr = err as AxiosError<any>;
const mensaje = axiosErr?.response?.data?.message || 'No se pudo enviar el cuestionario, por favor intentelo mas tarde.'
Swal.fire('Error', mensaje, 'error')
// let message = 'Error al enviar el formulario';
// if (typeof err === 'object' && err !== null && 'response' in err) {
// const anyErr = err as { response?: { data?: { message?: unknown } } };
// if (anyErr.response?.data?.message) message = String(anyErr.response.data.message);
// } else if (err instanceof Error) message = err.message;
// window.alert(message);
} finally {
setIsLoading(false);
}
@@ -290,7 +344,7 @@ export default function FullCuestionario() {
{p.opciones?.map((op) => (
<div key={p.id + '-' + op} className="SINO">
<label>
<input type="radio" name={p.id} checked={respuestas[p.id] === op} onChange={() => updateRespuestas(p.id, op)} /> {op}
<input type="radio" name={p.id} checked={respuestas[p.id] === op} onChange={() => updateRespuestas(p.id, op)} disabled={isFormDisabled} /> {op}
</label>
</div>
))}
@@ -300,8 +354,8 @@ export default function FullCuestionario() {
<div>
{p.opciones?.map((op) => (
<div key={op} className="SINO">
<label>
<input className="checkb" type="checkbox" value={op} checked={Array.isArray(respuestas[p.id]) && (respuestas[p.id] as string[]).includes(op)} onChange={(e) => toggleSelection(p.id, op, e)} /> {op}
<label className="mt-2">
<input className="checkb" type="checkbox" value={op} checked={Array.isArray(respuestas[p.id]) && (respuestas[p.id] as string[]).includes(op)} onChange={(e) => toggleSelection(p.id, op, e)} disabled={isFormDisabled} /> {op}
</label>
</div>
))}
@@ -309,7 +363,7 @@ export default function FullCuestionario() {
)}
{p.tipo === 'texto' && (
<div>
<input id={p.id} className="form-control" type="text" value={(respuestas[p.id] as string) || ''} onChange={(e) => updateRespuestas(p.id, e.target.value)} maxLength={p.limite || 200} placeholder="Escribe tu respuesta aquí" />
<input id={p.id} className="form-control bg-transparent" type="text" value={(respuestas[p.id] as string) || ''} onChange={(e) => updateRespuestas(p.id, e.target.value)} maxLength={p.limite || 200} placeholder="Escribe tu respuesta aquí" disabled={isFormDisabled} />
</div>
)}
</div>
@@ -327,19 +381,19 @@ export default function FullCuestionario() {
<table className="table table-bordered text-center">
<thead>
<tr>
<th></th>
<th className="bg-transparent"></th>
{t.renglones[0].opciones.map((op) => (
<th key={op}>{op}</th>
<th className="bg-transparent" key={op}>{op}</th>
))}
</tr>
</thead>
<tbody>
{t.renglones.map((r) => (
<tr key={r.idRenglon}>
<td>{r.textoRenglon}</td>
<td className="bg-transparent">{r.textoRenglon}</td>
{r.opciones.map((op) => (
<td key={op}>
<input type="radio" name={`tabla-${t.idTabla}-renglon-${r.idRenglon}`} value={op} checked={Boolean(tablaResp && tablaResp[r.idRenglon] === op)} onChange={() => handleTableResponse(t.idTabla, r.idRenglon, op)} />
<td className="bg-transparent" key={op}>
<input type="radio" name={`tabla-${t.idTabla}-renglon-${r.idRenglon}`} value={op} checked={Boolean(tablaResp && tablaResp[r.idRenglon] === op)} onChange={() => handleTableResponse(t.idTabla, r.idRenglon, op)} disabled={isFormDisabled} />
</td>
))}
</tr>
@@ -351,12 +405,15 @@ export default function FullCuestionario() {
);
})}
<div className="my-6 mb-6 has-text-centered">
<div>
<button className="button is-success is-medium" onClick={submitForm} disabled={!isFormComplete}>
<div className="my-6 mb-6 text-center mb-5">
<button className="btn btn-success is-medium" onClick={submitForm} disabled={!isFormComplete || isFormDisabled || isLoading}>
{isLoading ? 'Enviando...' : 'Enviar'}
</button>
</div>
{isFormDisabled && (
<div className="alert alert-info mt-3" role="alert">
Este cuestionario ya ha sido contestado y no puede ser modificado.
</div>
)}
</div>
</div>
+98 -97
View File
@@ -19,9 +19,9 @@ interface Carrera {
}
export interface Servicio {
Programa: Programa;
Usuario: Usuario;
Carrera: Carrera;
programa: Programa;
//usuario: Usuario;
carrera: Carrera;
creditos?: string;
telefono?: string;
direccion?: string;
@@ -50,116 +50,117 @@ export default function InformacinoServicio({ servicio }: Props) {
};
return (
<div>
{/* Datos del programa */}
<div className="mb-5">
<h4 className="is-size-4 pb-2">Datos del programa</h4>
<div className="container">
{/* Datos del programa */}
<div className="mb-5">
<h4 className="is-size-4 pb-2">Datos del programa</h4>
<div className="mb-2">
<label>Institución:</label>
<p className="input">{servicio.Programa.institucion}</p>
<div className="mb-2">
<label className="form-label fw-semibold">Institución:</label>
<p className="form-control">{servicio.programa.institucion}</p>
</div>
<div className="mb-2">
<label className="form-label fw-semibold">Dependencia:</label>
<p className="form-control">{servicio.programa.dependencia}</p>
</div>
<div className="mb-2">
<label className="form-label fw-semibold">Programa:</label>
<p className="form-control">{servicio.programa.programa}</p>
</div>
<div className="mb-2">
<label className="form-label fw-semibold">Clave de programa:</label>
<p className="form-control">{servicio.programa.clavePrograma}</p>
</div>
</div>
<div className="mb-2">
<label>Dependencia:</label>
<p className="input">{servicio.Programa.dependencia}</p>
</div>
{/* Datos personales */}
<div className="mb-5">
<h4 className="is-size-4 pb-2">Datos personales</h4>
<div className="mb-2">
<label>Programa:</label>
<p className="input">{servicio.Programa.programa}</p>
</div>
{/*
<div className="mb-2">
<label className="form-label fw-semibold">Número de cuenta:</label>
<p className="form-control">{servicio.usuario.usuario}</p>
</div>
<div className="mb-2">
<label>Clave de programa:</label>
<p className="input">{servicio.Programa.clavePrograma}</p>
</div>
</div>
<div className="mb-2">
<label className="form-label fw-semibold">Nombre:</label>
<p className="form-control">{servicio.usuario.nombre}</p>
</div>
*/}
<div className="mb-2">
<label className="form-label fw-semibold">Carrera:</label>
<p className="form-control">{servicio.carrera.carrera}</p>
</div>
{/* Datos personales */}
<div className="mb-5">
<h4 className="is-size-4 pb-2">Datos personales</h4>
<div className="mb-2">
<label className="form-label fw-semibold">Créditos:</label>
<p className="form-control">
{servicio.creditos ? parseInt(servicio.creditos) : ""}
{servicio.creditos && "%"}
</p>
</div>
<div className="mb-2">
<label>Número de cuenta:</label>
<p className="input">{servicio.Usuario.usuario}</p>
</div>
{servicio.telefono && (
<div className="mb-2">
<label className="form-label">Teléfono:</label>
<p className="form-control">{servicio.telefono}</p>
</div>
)}
<div className="mb-2">
<label>Nombre:</label>
<p className="input">{servicio.Usuario.nombre}</p>
</div>
{servicio.direccion && (
<div className="mb-2">
<label className="form-label fw-semibold">Dirección:</label>
<p className="form-control">{servicio.direccion}</p>
</div>
)}
<div className="mb-2">
<label>Carrera:</label>
<p className="input">{servicio.Carrera.carrera}</p>
</div>
<div className="mb-2">
<label className="form-label fw-semibold">Email:</label>
<p className="form-control">{servicio.correo}</p>
</div>
<div className="mb-2">
<label>Créditos:</label>
<p className="input">
{servicio.creditos ? parseInt(servicio.creditos) : ""}
{servicio.creditos && "%"}
</p>
</div>
{servicio.programaInterno && (
<div className="mb-2">
<label className="form-label fw-semibold">Programa Interno:</label>
<p className="form-control">{servicio.programaInterno}</p>
</div>
)}
{servicio.telefono && (
<div className="mb-2">
<label>Teléfono:</label>
<p className="input">{servicio.telefono}</p>
</div>
)}
{servicio.profesor && (
<div className="mb-2">
<label className="form-label fw-semibold">Profesor:</label>
<p className="form-control">{servicio.profesor}</p>
</div>
)}
{servicio.direccion && (
<div className="mb-2">
<label>Dirección:</label>
<p className="input">{servicio.direccion}</p>
</div>
)}
{servicio.createdAt && (
<div className="mb-2">
<label className="form-label fw-semibold">Fecha de registro:</label>
<p className="form-control">{fecha(servicio.createdAt)}</p>
</div>
)}
<div className="mb-2">
<label>Email:</label>
<p className="input">{servicio.correo}</p>
</div>
<div className="mb-2">
<label className="form-label fw-semibold">Fecha de inicio:</label>
<p className="form-control">{fecha(servicio.fechaInicio)}</p>
</div>
{servicio.programaInterno && (
<div className="mb-2">
<label>Programa Interno:</label>
<p className="input">{servicio.programaInterno}</p>
</div>
)}
<div className="mb-2">
<label className="form-label fw-semibold">Fecha de término:</label>
<p className="form-control">{fecha(servicio.fechaFin)}</p>
</div>
{servicio.profesor && (
<div className="mb-2">
<label>Profesor:</label>
<p className="input">{servicio.profesor}</p>
{servicio.fechaLiberacion && (
<div className="mb-2">
<label className="form-label fw-semibold">Fecha de liberación:</label>
<p className="form-control">{fecha(servicio.fechaLiberacion)}</p>
</div>
)}
</div>
)}
{servicio.createdAt && (
<div className="mb-2">
<label>Fecha de registro:</label>
<p className="input">{fecha(servicio.createdAt)}</p>
</div>
)}
<div className="mb-2">
<label>Fecha de inicio:</label>
<p className="input">{fecha(servicio.fechaInicio)}</p>
</div>
<div className="mb-2">
<label>Fecha de término:</label>
<p className="input">{fecha(servicio.fechaFin)}</p>
</div>
{servicio.fechaLiberacion && (
<div className="mb-2">
<label>Fecha de liberación:</label>
<p className="input">{fecha(servicio.fechaLiberacion)}</p>
</div>
)}
</div>
</div>
);
}
+11 -11
View File
@@ -5,16 +5,16 @@ interface Status {
}
interface Props {
Status: Status;
status: Status;
}
export default function MensajeAlumno({ Status }: Props) {
export default function MensajeAlumno({ status }: Props) {
return (
<div className="mb-6">
<h4 className="block is-size-4">Estimado alumno(a):</h4>
<div className="container mb-6">
<h4 className="block is-size-4 mb-4">Estimado alumno(a):</h4>
<p className="has-text-justified is-size-6">
{Status.idStatus === 2 && (
{status.idStatus === 2 && (
<>
Estimado alumno(a): Para poder terminar con el registro de tu trámite
de servicio social te pedimos que verifiques que tus datos sean
@@ -27,7 +27,7 @@ export default function MensajeAlumno({ Status }: Props) {
</>
)}
{Status.idStatus === 3 && (
{status.idStatus === 3 && (
<>
Estimado alumno(a): Te informamos que el Área de Registro y Control
de Servicio Social ha validado tu solicitud de registro de servicio
@@ -49,7 +49,7 @@ export default function MensajeAlumno({ Status }: Props) {
</>
)}
{Status.idStatus === 4 && (
{status.idStatus === 4 && (
<>
Estimado alumno(a): Para continuar con el proceso de término de tu
servicio social debes de subir tu informe global y contestar el
@@ -57,14 +57,14 @@ export default function MensajeAlumno({ Status }: Props) {
</>
)}
{Status.idStatus === 5 && (
{status.idStatus === 5 && (
<>
Estimado alumno(a): Espera a que tus documentos sean validados por
el Departamento de Servicio Social y Bolsa de Trabajo.
</>
)}
{Status.idStatus === 6 && (
{status.idStatus === 6 && (
<>
Estimado alumno(a): Te confirmamos que has concluido con los trámites
necesarios para la liberación de tu servicio social por lo que ahora
@@ -77,7 +77,7 @@ export default function MensajeAlumno({ Status }: Props) {
</>
)}
{Status.idStatus === 8 && (
{status.idStatus === 8 && (
<>
Estimado alumno(a): Te informamos que la carta de término que el(la)
responsable de tu servicio social mandó fue rechazada. Espera a que
@@ -85,7 +85,7 @@ export default function MensajeAlumno({ Status }: Props) {
</>
)}
{Status.idStatus === 9 && (
{status.idStatus === 9 && (
<>
Estimado alumno(a): Te informamos que el informe global que mandaste
fue rechazado. Súbelo nuevamente y asegúrate de que cumpla con todos
+164 -71
View File
@@ -1,17 +1,23 @@
"use client";
import { useState, useEffect } from "react";
import { axiosInstance } from "@/api/config";
import { isAxiosError } from 'axios';
import axios from 'axios';
import { Button, FormGroup } from "react-bootstrap";
import { FaUpload } from "react-icons/fa6";
import { useRouter } from "next/navigation";
import Swal from "sweetalert2";
import Link from "next/link";
interface Alumno {
tokenArchivo: string;
tokenAlumno: string;
}
interface Servicio {
idServicio: number;
idCuestionarioAlumno?: number;
idCuestionarioAlumno2?: number;
//idCuestionarioAlumno?: number;
cuestionarioAlumno?: object;
//idCuestionarioAlumno2?: number;
cuestionarioAlumno2?: object;
informeGlobal?: string;
}
@@ -35,14 +41,39 @@ export default function PreTermino({
updateIsLoading,
}: Props) {
const [file, setFile] = useState<File | null>(null);
const router = useRouter();
const [servicioLocal, setServicioLocal] = useState<Servicio>(servicio);
useEffect(() => {
setServicioLocal(servicio);
}, [servicio]);
useEffect(() => {
console.log("Esta es la informacion que se le pasa verificacion cuestionario alumno", servicio)
if (file && file.size >= 20000000) {
imprimirError({ message: "El tamaño del archivo excede los 20MB" });
setFile(null);
}
}, [file, imprimirError]);
const confirmarInforme = () => {
Swal.fire({
title: "¿Estas seguro(a) de querer subir este informe global?",
icon: "warning",
showCancelButton: true,
confirmButtonText: "Sí, actualizar",
cancelButtonText: "Cancelar",
confirmButtonColor: "#0d6efd",
cancelButtonColor: "#dc3545",
}).then((result) => {
if(result.isConfirmed) {
enviarInformeGlobal();
}
})
}
const enviarInformeGlobal = async () => {
if (!file) return;
@@ -52,96 +83,158 @@ export default function PreTermino({
formData.append("data", JSON.stringify(data));
formData.append("informeGlobal", file);
console.log("Token del alumno:", alumno.tokenAlumno);
console.log("Alumno completo: ", alumno)
try {
updateIsLoading(true);
const res = await axiosInstance.put(`/servicio/informe_global`, formData, { headers: { "Content-Type": "multipart/form-data", Authorization: alumno.tokenArchivo } });
imprimirMensaje(res.data.message);
obtenerServicio();
setFile(null);
} catch (err: unknown) {
if (isAxiosError(err)) {
imprimirError((err.response?.data) || err.message);
} else {
imprimirError(err);
}
updateIsLoading(true);
// Cambiamos a la otra direccion usamos variable de .env
const res = await axios.put(`${process.env.NEXT_PUBLIC_API_URL}/servicio/informe_global`, formData, {
headers: {
// "Content-Type": "multipart/form-data",
Authorization: `Bearer ${alumno.tokenAlumno}`
}
});
Swal.fire('Exito', 'Se envio el informe global correctamente.', 'success')
setServicioLocal(prev => ({
...prev,
informeGlobal: "uploaded"
}));
// await obtenerServicio();
setFile(null);
// setTimeout(() => {
// router.refresh();
// }, 1000);
//router.refresh(); // Para recargar la pagina y valide si tiene el informe.
} catch (err) {
Swal.fire('Error', 'No se pudo enviar el archivo, por favor intentelo mas tarde.', 'error')
// if (isAxiosError(err)) {
// imprimirError((err.response?.data) || err.message);
// } else {
// imprimirError(err);
// }
} finally {
updateIsLoading(false);
updateIsLoading(false);
}
};
return (
<div>
<h3 className="label">
<div className="container">
<h6 className="label fw-semibold">
Cuestionario de evaluación del programa de servicio social.
</h3>
</h6>
{!servicio.idCuestionarioAlumno && !servicio.idCuestionarioAlumno2 ? (
<div className="mb-6">
<a
href="/alumno/cuestionario"
className="button is-info is-light"
>
<span className="icon">
<i className="fas fa-book-open"></i>
</span>
<span>Cuestionario</span>
</a>
{/* !servicio.idCuestionarioAlumno && !servicio.idCuestionarioAlumno2 ? */}
{servicio.cuestionarioAlumno2 == null && servicio.cuestionarioAlumno == null ? (
<div className="mb-5">
<Link
href={`/alumno/cuestionario/${servicio.idServicio}`}
className="btn btn-outline-primary is-info is-light"
>
<span className="icon">
<i className="fas fa-book-open"></i>
</span>
<span>Cuestionario</span>
</Link>
</div>
) : (
<div className="mb-6">
<p className="block is-size-6">
Cuestionario contestado{" "}
<span className="icon has-text-success">
<i className="fas fa-check-bold"></i>
</span>
</p>
<p className="block is-size-6">
Cuestionario contestado{" "}
<span className="icon has-text-success">
<i className="fas fa-check-bold"></i>
</span>
</p>
</div>
)}
<h3 className="label">
<h6 className="label fw-semibold">
Informe global de actividades{" "}
{!servicio.informeGlobal && (
{servicioLocal.informeGlobal && (
<span>(en formato .PDF. No se aceptan fotos)</span>
)}
.
</h3>
</h6>
{!servicio.informeGlobal ? (
<div>
<div className="field">
<input
type="file"
accept="application/pdf"
onChange={(e) => {
if (e.target.files && e.target.files[0]) {
setFile(e.target.files[0]);
{/*
Codigo anterior para el envio del archivo
<div>
<div className="field">
<input
type="file"
accept="application/pdf"
onChange={(e) => {
if (e.target.files && e.target.files[0]) {
setFile(e.target.files[0]);
}
}}
/>
<p className="is-size-6">Tamaño máximo 20MB</p>
</div>
<div className="field has-text-centered my-5">
<button
className="button is-success"
disabled={!file}
onClick={() =>
imprimirWarning(
"¿Estas seguro(a) de querer subir este informe global?",
enviarInformeGlobal
)
}
}}
/>
<p className="is-size-6">Tamaño máximo 20MB</p>
>
Enviar
</button>
</div>
</div>
*/}
<div className="field has-text-centered my-5">
<button
className="button is-success"
disabled={!file}
onClick={() =>
imprimirWarning(
"¿Estas seguro(a) de querer subir este informe global?",
enviarInformeGlobal
)
}
>
Enviar
</button>
</div>
{!servicioLocal.informeGlobal ? (
<div className="ph-5">
<FormGroup>
<div className="border p-4 text-center rounded"
style={{ cursor: 'pointer'}}
onClick={() => document.getElementById("fileInput")?.click()}
>
<FaUpload size={40} className="mb-2"/>
<p className="mb-1">
{file?.name || 'Arrastra aquì tu archivo o da click aquì para buscar'}
</p>
<p className="is-size-6">Tamaño maximo 20MB</p>
<p className="is-size-7">Sia al momento de elegir un archivo este no se selecciona, haga click en cancelar en la ventana emergente e intente de nuevo.</p>
</div>
<input
id="fileInput"
type="file"
accept="application/pdf"
onChange={(e) => {
if (e.target.files && e.target.files[0]) {
setFile(e.target.files[0]);
}
}}
hidden
/>
</FormGroup>
<div className="d-flex gap-2 justify-content-center mb-5">
<Button
className=""
disabled={!file}
onClick={confirmarInforme}
>Enviar</Button>
</div>
</div>
) : (
<p className="block is-size-6">
Informe Global enviado.{" "}
<span className="icon has-text-success">
<i className="fas fa-check-bold"></i>
</span>
<p className="block is-size-6 mb-5">
Informe Global enviado.{" "}
<span className="icon has-text-success">
<i className="fas fa-check-bold"></i>
</span>
</p>
)}
</div>
+543 -238
View File
@@ -1,11 +1,22 @@
'use client';
import React, { useState, useEffect } from 'react';
import { useState, useEffect } from 'react';
import { axiosInstance } from '@/api/config';
import moment from 'moment';
import 'bootstrap/dist/css/bootstrap.min.css';
import { Col, FormGroup, FormLabel, InputGroup } from 'react-bootstrap';
import { FaRegCalendar, FaUpload } from 'react-icons/fa6';
import DatePicker from 'react-datepicker';
import BotonRegresar from '../boton-regresar';
import axios, { AxiosError } from "axios";
import Image from 'next/image';
import { registerLocale } from "react-datepicker";
import { es } from "date-fns/locale/es";
import Swal from 'sweetalert2';
// 🔹 Tipos estrictos
registerLocale("es", es);
5
interface Alumno {
idUsuario?: number;
idCarrera?: number;
@@ -14,21 +25,29 @@ interface Alumno {
creditos?: string;
}
// correo);
// formData.append("alumno[creditos]", alumno.creditos.toString());
// formData.append("alumno[telefono]", alumno.telefono);
// formData.append("alumno[fechaInicio]", alumno.fechaInicio);
// formData.append("alumno[fechaFin]", alumno.fechaFin);
// formData.append("alumno[fechaNacimiento]", alumno.fechaNacimiento);
// formData.append("alumno[direccion]", alumno.direccion);
interface Responsable {
token: { headers: Record<string, string> };
tokenArchivo: { headers: Record<string, string> };
//token: { headers: Record<string, string> };
tokenArchivo: string;
}
interface Props {
responsable: Responsable;
// responsable: Responsable;
imprimirError: (msg: { message: string }) => void;
imprimirMensaje: (msg: string) => void;
imprimirWarning: (msg: string, callback: () => void) => void;
imprimirWarning: (msg: string, callback: () => void, title: string) => void;
updateIsLoading: (value: boolean) => void;
}
export default function CasoEspecialForm({
responsable,
// responsable,
imprimirError,
imprimirMensaje,
imprimirWarning,
@@ -46,11 +65,32 @@ export default function CasoEspecialForm({
const [alumno, setAlumno] = useState<Alumno>({});
const [fechaInicio, setFechaInicio] = useState<Date>(new Date());
const [fechaFin, setFechaFin] = useState<Date>(new Date());
const [fechaNacimiento, setFechaNacimiento] = useState<Date>(new Date());
const [fechaNacimiento, setFechaNacimiento] = useState<Date | null>(null);
const [file, setFile] = useState<File | null>(null);
const [minDate, setMinDate] = useState<Date>(new Date('2020-01-02'));
const [minDate2, setMinDate2] = useState<Date>(new Date());
const [tokenArchivo, setTokenArchivo] = useState('');
useEffect(() => {
setTokenArchivo(localStorage.getItem('token') || '');
}, []);
console.log("Este es el tokenArchivo en caso especial form:", tokenArchivo);
// Para mostrar las fechas
const handleFechaInicioChange = (date: Date | null) => {
setFechaInicio(date || new Date());
}
const handleFechaFinChange = (date: Date | null) => {
setFechaFin(date || new Date());
}
const handleFechaNacimientoChange = (date: Date | null) => {
setFechaNacimiento(date)
}
// Función para resetear campos
const resetear = (): void => {
setDependencia('');
@@ -63,7 +103,7 @@ export default function CasoEspecialForm({
setAlumno({});
setFechaInicio(new Date());
setFechaFin(new Date());
setFechaNacimiento(new Date());
setFechaNacimiento(null);
setFile(null);
};
@@ -72,46 +112,54 @@ export default function CasoEspecialForm({
const permitidas = [/.zip$/i, /.rar$/i];
const esValida = permitidas.some((regex) => regex.test(archivo.name));
if (!esValida) {
setFile(null);
imprimirError({
message: 'Asegúrate de ingresar un archivo con la extensión correcta (.zip o .rar).',
});
setFile(null);
imprimirError({
message: 'Asegúrate de ingresar un archivo con la extensión correcta (.zip o .rar).',
});
}
};
// Mostrar/ocultar botón de enviar
const mostrarBoton = (): boolean => {
return (
!alumno.idUsuario ||
!telefono ||
!direccion ||
!correo ||
!idStatus ||
(idStatus === '11' && !motivo) ||
(idStatus === '12' && (!dependencia || !institucion)) ||
!file
!alumno.idUsuario ||
!telefono ||
!direccion ||
!correo ||
!idStatus ||
(idStatus === '11' && !motivo) ||
(idStatus === '12' && (!dependencia || !institucion)) ||
!file
);
};
// Buscar alumno
const buscarAlumno = async (): Promise<void> => {
try {
updateIsLoading(true);
const res = await axiosInstance.get(`/usuario/escolares?numeroCuenta=${numeroCuenta}`, responsable.token);
resetear();
setAlumno(res.data);
updateIsLoading(true);
const res = await axiosInstance.post(`/usuario/escolares/${numeroCuenta}`);
resetear();
console.log('Esta es la res:' , res)
setAlumno(res.data);
} catch (err: unknown) {
resetear();
setNumeroCuenta('');
let msg = { message: 'Error al buscar alumno.' };
if (typeof err === 'object' && err !== null && 'response' in err) {
const anyErr = err as { response?: { data?: { message?: unknown } } };
const m = anyErr.response?.data as { message?: unknown } | undefined;
msg = { message: m?.message ? String(m.message) : msg.message };
} else if (err instanceof Error) msg = { message: err.message };
imprimirError(msg);
resetear();
setNumeroCuenta('');
const axiosErr = err as AxiosError<any>;
const mensaje = axiosErr?.response?.data?.message || 'El alumno no cumple con los requisitos para hacer el servicio social.'
Swal.fire({
icon: 'error',
title: 'Error',
text: mensaje,
})
// let msg = { message: 'Error al buscar alumno.' };
// if (typeof err === 'object' && err !== null && 'response' in err) {
// const anyErr = err as { response?: { data?: { message?: unknown } } };
// const m = anyErr.response?.data as { message?: unknown } | undefined;
// msg = { message: m?.message ? String(m.message) : msg.message };
// } else if (err instanceof Error) msg = { message: err.message };
// imprimirError(msg);
} finally {
updateIsLoading(false);
updateIsLoading(false);
}
};
@@ -119,52 +167,179 @@ export default function CasoEspecialForm({
const enviar = async (): Promise<void> => {
if (!file) return;
if (!tokenArchivo) {
imprimirError({ message: "Sesión expirada. Vuelve a iniciar sesión." });
return;
}
if (!fechaNacimiento) {
imprimirError({ message: "Selecciona la fecha de nacimiento" });
return;
}
if (!alumno.idUsuario || !alumno.idCarrera) {
imprimirError({ message: "Busca primero un alumno válido" });
return;
}
const data = {
idUsuario: alumno.idUsuario,
idCarrera: alumno.idCarrera,
idStatus,
numeroCuenta,
creditos: alumno.creditos,
correo,
fechaInicio: moment(fechaInicio),
fechaFin: moment(fechaFin),
fechaNacimiento: moment(fechaNacimiento),
direccion,
telefono,
institucion,
dependencia,
motivo,
idUsuario: Number(alumno.idUsuario),
idCarrera: Number(alumno.idCarrera),
idStatus: Number(idStatus),
numeroCuenta: numeroCuenta,
creditos: alumno.creditos ? alumno.creditos.toString() : '',
correo: correo.trim(),
fechaInicio: formatDate(fechaInicio),
fechaFin: formatDate(fechaFin),
fechaNacimiento: formatDate(fechaNacimiento),
direccion: direccion.trim(),
telefono: telefono,
...(idStatus === '12' && { institucion: institucion.trim() }),
...(idStatus === '12' && { dependencia: dependencia.trim() }),
...(idStatus === '11' && { motivo }),
// idUsuario: Number('19839'),
// idCarrera: Number('16'),
// idStatus: Number('11'),
// numeroCuenta: '422016698',
// creditos: '100',
// correo: correo.trim(),
// fechaInicio: formatDate(fechaInicio),
// fechaFin: formatDate(fechaFin),
// fechaNacimiento: formatDate(fechaNacimiento),
// direccion: direccion.trim(),
// telefono: telefono,
// ...(idStatus === '12' && { institucion: institucion.trim() }),
// ...(idStatus === '12' && { dependencia: dependencia.trim() }),
// ...(idStatus === '11' && { motivo }),
};
console.log("Datos a enviar en caso especial:", data);
const formData = new FormData();
formData.append('alumno', JSON.stringify(data));
formData.append('archivos', file);
// agregar el archivo.
formData.append("archivos", file);
formData.append("alumno", JSON.stringify(data));
// armas los datos para el dto
// formData.append("alumno[idUsuario]", alumno.idUsuario.toString());
// formData.append("alumno[idCarrera]", alumno.idCarrera.toString());
// formData.append("alumno[idStatus]", idStatus);
// formData.append("alumno[numeroCuenta]", numeroCuenta);
// formData.append("alumno[correo]", correo);
// formData.append("alumno[creditos]", alumno.creditos || "");
// formData.append("alumno[telefono]", telefono);
// formData.append("alumno[fechaInicio]", formatDate(fechaInicio));
// formData.append("alumno[fechaFin]", formatDate(fechaFin));
// formData.append("alumno[fechaNacimiento]", formatDate(fechaNacimiento));
// formData.append("alumno[direccion]", direccion);
// validaciones extra
// if (institucion) {
// formData.append("alumno[institucion]", institucion);
// }
// if (dependencia) {
// formData.append("alumno[dependencia]", dependencia);
// }
// if (motivo) {
// formData.append("alumno[motivo]", motivo);
// }
// if (!idStatus) {
// console.log('No se envio el idStatus')
// }
//const info = formData.append('alumno', new Blob([JSON.stringify(data)], { type: 'application/json' }));
// formData.append('archivos', file);
// formData.append("alumno[idUsuario]", alumno.idUsuario.toString());
// formData.append("alumno[idCarrera]", alumno.idCarrera.toString());
// formData.append("alumno[idStatus]", alumno.idStatus.toString());
// formData.append("alumno[numeroCuenta]", alumno.numeroCuenta);
// formData.append("alumno[correo]", alumno.correo);
// formData.append("alumno[creditos]", alumno.creditos.toString());
// formData.append("alumno[telefono]", alumno.telefono);
// formData.append("alumno[fechaInicio]", alumno.fechaInicio);
// formData.append("alumno[fechaFin]", alumno.fechaFin);
// formData.append("alumno[fechaNacimiento]", alumno.fechaNacimiento);
// formData.append("alumno[direccion]", alumno.direccion);
console.log("FormData a enviar en caso especial:", formData.getAll);
console.log("Esta es la info al registrar el servicio", formData);
try {
updateIsLoading(true);
const res = await axiosInstance.post(`/caso_especial/nuevo`, formData, responsable.tokenArchivo);
resetear();
setNumeroCuenta('');
imprimirMensaje(res.data.message);
updateIsLoading(true);
// Cambiamos a axios normal para enviar la carta de aceptación
// const res = await axiosInstance.post(`/caso-especial`, formData);
const res = await axios.post(`${process.env.NEXT_PUBLIC_API_URL}/caso-especial`, formData, {
headers: {
'Content-Type': 'multipart/form-data',
Authorization: tokenArchivo ? `Bearer ${tokenArchivo}` : '',
},
});
resetear();
setNumeroCuenta('');
const mensaje = res?.data?.message || 'Se creo el servicio social con exito.'
Swal.fire({
icon: 'success',
title: 'Exito',
text: mensaje,
})
// imprimirMensaje(res.data.message);
} catch (err: unknown) {
let msg: { message: string } = { message: 'Error al enviar formulario.' };
if (typeof err === 'object' && err !== null && 'response' in err) {
const anyErr = err as { response?: { data?: unknown } };
if (anyErr.response && typeof anyErr.response.data === 'object' && anyErr.response.data !== null) {
const d = anyErr.response.data as { message?: unknown } | unknown;
if (d && typeof d === 'object' && 'message' in d && typeof (d as { message?: unknown }).message === 'string') {
msg = { message: (d as { message?: unknown }).message as string };
}
}
} else if (err instanceof Error) {
msg = { message: err.message };
}
imprimirError(msg);
const axiosErr = err as AxiosError<any>;
const mensaje = axiosErr?.response?.data?.message || 'No se pudo completar el registro del servicio social, por favor intentelo mas tarde.'
Swal.fire({
icon: 'error',
title: 'Error',
text: mensaje,
})
// Quitar esta parte
// if (axios.isAxiosError(err)) {
// const backendMessage = err.response?.data?.message;
// if (backendMessage) {
// // Si el backend manda string
// if (typeof backendMessage === "string") {
// imprimirError({ message: backendMessage });
// return;
// }
// // Si el backend manda arreglo (NestJS validation)
// if (Array.isArray(backendMessage)) {
// imprimirError({ message: backendMessage.join("\n") });
// return;
// }
// }
// Fallback SOLO si no vino nada del backend
// imprimirError({ message: "Error inesperado del servidor" });
// let msg: { message: string } = { message: 'Error al enviar formulario.' };
// if (typeof err === 'object' && err !== null && 'response' in err) {
// const anyErr = err as { response?: { data?: unknown } };
// if (anyErr.response && typeof anyErr.response.data === 'object' && anyErr.response.data !== null) {
// const d = anyErr.response.data as { message?: unknown } | unknown;
// if (d && typeof d === 'object' && 'message' in d && typeof (d as { message?: unknown }).message === 'string') {
// msg = { message: (d as { message?: unknown }).message as string };
// }
// }
// } else if (err instanceof Error) {
// msg = { message: err.message };
// }
// imprimirError(msg);
} finally {
updateIsLoading(false);
updateIsLoading(false);
}
};
const formatDate = (date: Date) => {
return date.toISOString().split("T")[0];
};
// Actualizar fechas automáticamente
const updateFechas = (): void => {
const nuevaFin = new Date(
@@ -184,11 +359,11 @@ export default function CasoEspecialForm({
}, [idStatus]);
useEffect(() => {
if (file && file.size >= 20_000_000) {
imprimirError({ message: 'El tamaño del archivo excede los 20MB.' });
setFile(null);
if (file && file.size > 20_000_000) {
imprimirError({ message: 'El tamaño del archivo excede los 20MB.' });
setFile(null);
} else if (file) {
validarExtencion(file);
validarExtencion(file);
}
}, [file]);
@@ -203,195 +378,325 @@ export default function CasoEspecialForm({
updateFechas();
}, []);
const confirmarCasoEspecial = () => {
Swal.fire({
})
}
{/* Validaciones del cuestionario */}
// Render
return (
<div className="container mt-4">
<div className="mb-3">
<label className="form-label">Número de Cuenta</label>
<div className="input-group">
<input
<div className="mt-4">
<div className="mb-3">
<label className="form-label">Número de Cuenta</label>
<div className="input-group">
<InputGroup.Text>
<Image src='/image/numCuenta.svg' width={24} height={24} alt="Icono de escuela"/>
</InputGroup.Text>
<input
type="text"
className="form-control"
placeholder="Número de Cuenta"
maxLength={9}
value={numeroCuenta}
onChange={(e) => setNumeroCuenta(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && buscarAlumno()}
/>
<button className="text-white rounded-1" onClick={buscarAlumno}>
Buscar
</button>
</div>
</div>
{alumno.nombre && (
<div className="mb-3">
<label className="form-label">Nombre</label>
<p className="form-control">{alumno.nombre || ''}</p>
</div>
)}
{alumno.carrera && (
<div className="mb-3">
<label className="form-label">Carrera</label>
<p className="form-control">{alumno.carrera || ''}</p>
</div>
)}
{alumno.creditos && (
<div className="mb-3">
<label className="form-label">Créditos</label>
<p className="form-control">
{alumno.creditos ? parseInt(alumno.creditos) : ''}{' '}
{alumno.creditos ? '%' : ''}
</p>
</div>
)}
<Col>
<FormGroup>
<FormLabel>Fecha de nacimiento</FormLabel>
<InputGroup>
<InputGroup.Text>
<FaRegCalendar />
</InputGroup.Text>
<DatePicker
// selected={fechaNacimiento}
selected={fechaNacimiento}
// maxDate={new Date()}
onChange={handleFechaNacimientoChange}
dateFormat="dd-MM-yyyy"
className="form-control"
wrapperClassName="flex-grow-1"
calendarClassName="mi-calendario"
showMonthDropdown
showYearDropdown
scrollableYearDropdown
yearDropdownItemNumber={60}
dropdownMode="select"
locale='es'
/>
</InputGroup>
</FormGroup>
</Col>
<div className="mb-3">
<label className="form-label">Dirección</label>
<input
type="text"
className="form-control"
placeholder="Número de Cuenta"
maxLength={9}
value={numeroCuenta}
onChange={(e) => setNumeroCuenta(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && buscarAlumno()}
/>
<button className="btn btn-info text-white" onClick={buscarAlumno}>
Buscar
</button>
maxLength={200}
value={direccion}
onChange={(e) => setDireccion(e.target.value)}
/>
</div>
</div>
<div className="mb-3">
<label className="form-label">Nombre</label>
<p className="form-control">{alumno.nombre || ''}</p>
</div>
<div className="mb-3">
<label className="form-label">Carrera</label>
<p className="form-control">{alumno.carrera || ''}</p>
</div>
<div className="mb-3">
<label className="form-label">Créditos</label>
<p className="form-control">
{alumno.creditos ? parseInt(alumno.creditos) : ''}{' '}
{alumno.creditos ? '%' : ''}
</p>
</div>
<div className="mb-3">
<label className="form-label">Dirección</label>
<input
type="text"
className="form-control"
maxLength={200}
value={direccion}
onChange={(e) => setDireccion(e.target.value)}
/>
</div>
<div className="mb-3">
<label className="form-label">Teléfono</label>
<input
type="tel"
className="form-control"
maxLength={10}
value={telefono}
onChange={(e) => setTelefono(e.target.value)}
/>
</div>
<div className="mb-3">
<label className="form-label">Correo electrónico</label>
<input
type="email"
className="form-control"
value={correo}
onChange={(e) => setCorreo(e.target.value)}
/>
</div>
<div className="mb-3">
<label className="form-label">Fecha de inicio</label>
<input
type="date"
className="form-control"
value={fechaInicio.toISOString().substring(0, 10)}
onChange={(e) => setFechaInicio(new Date(e.target.value))}
min={minDate.toISOString().substring(0, 10)}
/>
</div>
{fechaInicio && (
<div className="mb-3">
<label className="form-label">Fecha de fin</label>
<input
<label className="form-label">Teléfono</label>
<input
type="tel"
className="form-control"
maxLength={10}
value={telefono}
onChange={(e) => setTelefono(e.target.value)}
/>
</div>
<div className="mb-3">
<label className="form-label">Correo electrónico</label>
<input
type="email"
className="form-control"
value={correo}
onChange={(e) => setCorreo(e.target.value)}
/>
</div>
<Col>
<FormGroup>
<FormLabel>Fecha de inicio</FormLabel>
<InputGroup>
<InputGroup.Text>
<FaRegCalendar />
</InputGroup.Text>
<DatePicker
selected={fechaInicio}
onChange={handleFechaInicioChange}
minDate={minDate}
dateFormat="dd-MM-yyyy"
className="form-control"
wrapperClassName="flex-grow-1"
calendarClassName="mi-calendario"
showMonthDropdown
showYearDropdown
scrollableYearDropdown
yearDropdownItemNumber={15}
dropdownMode="select"
locale='es'
/>
</InputGroup>
</FormGroup>
</Col>
{fechaInicio && (
<Col>
<FormGroup>
<FormLabel>Fecha de fin</FormLabel>
<InputGroup>
<InputGroup.Text>
<FaRegCalendar />
</InputGroup.Text>
<DatePicker
selected={fechaFin}
onChange={handleFechaFinChange}
minDate={minDate2}
dateFormat="dd-MM-yyyy"
className="form-control"
wrapperClassName="flex-grow-1"
calendarClassName="mi-calendario"
showMonthDropdown
showYearDropdown
scrollableYearDropdown
yearDropdownItemNumber={15}
dropdownMode="select"
locale='es'
/>
</InputGroup>
</FormGroup>
</Col>
)}
{/*
<div className="mb-3">
<label className="form-label">Fecha de inicio</label>
<input
type="date"
className="form-control"
value={fechaFin.toISOString().substring(0, 10)}
onChange={(e) => setFechaFin(new Date(e.target.value))}
min={minDate2.toISOString().substring(0, 10)}
/>
value={fechaInicio.toISOString().substring(0, 10)}
onChange={(e) => setFechaInicio(new Date(e.target.value))}
min={minDate.toISOString().substring(0, 10)}
/>
</div>
)}
<div className="mb-3">
<label className="form-label">Fecha de nacimiento</label>
<input
type="date"
className="form-control"
max={new Date().toISOString().substring(0, 10)}
value={fechaNacimiento.toISOString().substring(0, 10)}
onChange={(e) => setFechaNacimiento(new Date(e.target.value))}
/>
</div>
{fechaInicio && (
<div className="mb-3">
<label className="form-label">Fecha de fin</label>
<input
type="date"
className="form-control"
value={fechaFin.toISOString().substring(0, 10)}
onChange={(e) => setFechaFin(new Date(e.target.value))}
min={minDate2.toISOString().substring(0, 10)}
/>
</div>
)}
<div className="mb-3">
<label className="form-label">Artículo</label>
<select
className="form-select"
value={idStatus}
onChange={(e) => setIdStatus(e.target.value)}
>
<option value="">Selecciona una opción</option>
<option value="11">Artículo 52</option>
<option value="12">Artículo 91</option>
</select>
</div>
{idStatus === '11' && (
<div className="mb-3">
<label className="form-label">Motivo</label>
<select
<label className="form-label">Fecha de nacimiento</label>
<input
type="date"
className="form-control"
max={new Date().toISOString().substring(0, 10)}
value={fechaNacimiento.toISOString().substring(0, 10)}
onChange={(e) => setFechaNacimiento(new Date(e.target.value))}
/>
</div>
*/}
<div className="mb-3">
<label className="form-label">Artículo</label>
<select
className="form-select"
value={motivo}
onChange={(e) => setMotivo(e.target.value)}
>
value={idStatus}
onChange={(e) => setIdStatus(e.target.value)}
>
<option value="">Selecciona una opción</option>
<option value="1">Tercera edad</option>
<option value="2">Capacidades diferentes</option>
</select>
<option value="11">Artículo 52</option>
<option value="12">Artículo 91</option>
</select>
</div>
)}
{idStatus === '12' && (
<>
<div className="mb-3">
<label className="form-label">Institución</label>
<input
type="text"
className="form-control"
maxLength={250}
value={institucion}
onChange={(e) => setInstitucion(e.target.value)}
{idStatus === '11' && (
<div className="mb-3">
<label className="form-label">Motivo</label>
<select
className="form-select"
value={motivo}
onChange={(e) => setMotivo(e.target.value)}
>
<option value="">Selecciona una opción</option>
<option value="1">Tercera edad</option>
<option value="2">Capacidades diferentes</option>
</select>
</div>
)}
{idStatus === '12' && (
<>
<div className="mb-3">
<label className="form-label">Institución</label>
<input
type="text"
className="form-control"
maxLength={250}
value={institucion}
onChange={(e) => setInstitucion(e.target.value)}
/>
</div>
<div className="mb-3">
<label className="form-label">Dependencia</label>
<input
type="text"
className="form-control"
maxLength={250}
value={dependencia}
onChange={(e) => setDependencia(e.target.value)}
/>
</div>
</>
)}
<FormGroup className='mb-3'>
<FormLabel>Archivo (.zip, .rar)</FormLabel>
<div
className='border p-4 text-center rounded'
style={{ cursor: 'pointer' }}
onClick={() => document.getElementById('fileInput')?.click()}
>
<FaUpload size={40} className='mb-2'/>
<p className='mb-1'>
{file?.name || 'Arrastra aquí tu archivo o da click aquí para buscar'}
</p>
<p className='is-size-6'>Tamaño máximo 20MB</p>
<p className='is-size-6'>Si al momento de elegir un archivo este no se selecciona, haga click en cancelar en la ventana emergente e intente de nuevo.</p>
</div>
<input
id='fileInput'
type="file"
accept='.zip, .rar'
style={{ display: 'none'}}
onChange={e => setFile(e.target.files ? e.target.files[0] : null)}
/>
</div>
</FormGroup>
<div className="mb-3">
<label className="form-label">Dependencia</label>
{/*
<div className="mb-3">
<label className="form-label">Archivo (.zip, .rar)</label>
<input
type="text"
type="file"
accept=".zip,.rar"
className="form-control"
maxLength={250}
value={dependencia}
onChange={(e) => setDependencia(e.target.value)}
onChange={(e) => setFile(e.target.files ? e.target.files[0] : null)}
/>
<div className="form-text">Tamaño máximo 20MB</div>
</div>
</>
)}
*/}
<div className="mb-3">
<label className="form-label">Archivo (.zip, .rar)</label>
<input
type="file"
accept=".zip,.rar"
className="form-control"
onChange={(e) => setFile(e.target.files ? e.target.files[0] : null)}
/>
<div className="form-text">Tamaño máximo 20MB</div>
</div>
<div className="text-center mt-4">
<button
className="btn btn-success"
disabled={mostrarBoton()}
onClick={() =>
imprimirWarning(
'¿Estás seguro(a) de querer crear un nuevo caso especial?',
enviar
)
}
>
Enviar
</button>
</div>
<div className="text-center mt-4 mb-3">
<button
className="btn btn-success"
disabled={mostrarBoton()}
onClick={() =>
imprimirWarning(
'¿Estás seguro(a) de querer crear un nuevo caso especial?',
enviar,
'Aviso'
)}
>
Enviar
</button>
</div>
<BotonRegresar />
</div>
);
}
@@ -3,6 +3,9 @@
import React, { useEffect, useState, useCallback } from "react";
import { axiosInstance } from "@/api/config";
import ServicioSocialTabla from "../servicio-social-tabla";
import { Button, Col, Form, FormControl, FormGroup, FormLabel, InputGroup, Row } from "react-bootstrap";
import { FaCircleInfo, FaUser } from "react-icons/fa6";
import Image from "next/image";
interface Responsable {
idTipoUsuario: number;
@@ -18,7 +21,7 @@ interface ServicioEspecial {
idServicio: number;
nombre: string;
numeroCuenta: string;
status: string;
Status: string;
// agrega más campos según lo que devuelva tu API
}
@@ -69,13 +72,9 @@ export default function LiberarCasoEspecial({ responsable, imprimirError }: Prop
if (search.nombre) query += `&nombre=${search.nombre}`;
if (search.numeroCuenta) query += `&numeroCuenta=${search.numeroCuenta}`;
const res = await axiosInstance.get(`/caso_especial/servicios_especiales?pagina=${page}${query}`, {
headers: {
Authorization: `Bearer ${responsable.token}`,
},
});
const res = await axiosInstance.get(`/caso-especial/servicios_especiales?pagina=${page}${query}`);
setData(res.data.serviciosEspeciales);
setData(res.data.data.serviciosEspeciales);
setTotal(res.data.count);
} catch (err: unknown) {
let msg = 'Error al obtener los casos especiales';
@@ -118,10 +117,84 @@ export default function LiberarCasoEspecial({ responsable, imprimirError }: Prop
return (
<div className="mb-6 container mt-4">
<h3 className="h4 mb-4">Casos Especiales</h3>
<h2 className="mb-4 fw-bold">Casos Especiales</h2>
<section className="my-3">
<Form onSubmit={(e) => { e.preventDefault(); obtenerCasosEspeciales(); }}>
<Row className="g-3">
<Col md={3}>
<FormGroup>
<FormLabel className="fw-semibold">Número de Cuenta</FormLabel>
<InputGroup>
<InputGroup.Text className="rounded-4">
<Image src="/image/numCuenta.svg" width={24} height={24} alt="Icono de escuela" />
</InputGroup.Text>
<FormControl
type="text"
placeholder="No.Cuenta"
maxLength={9}
value={search.numeroCuenta}
onChange={(e) => setSearch(prev => ({ ...prev, numeroCuenta: e.target.value }))}
onKeyDown={(e) => { if (e.key === 'Enter') obtenerCasosEspeciales(); }}
className="rounded-4"
/>
</InputGroup>
</FormGroup>
</Col>
<Col md={3}>
<FormGroup>
<FormLabel className="fw-semibold">Nombre</FormLabel>
<InputGroup>
<InputGroup.Text className="rounded-4">
<FaUser />
</InputGroup.Text>
<FormControl
type="text"
placeholder="Nombre"
value={search.nombre}
onChange={(e) => setSearch(prev => ({ ...prev, nombre: e.target.value }))}
onKeyDown={(e) => { if (e.key === 'Enter') obtenerCasosEspeciales(); }}
className="rounded-4"
/>
</InputGroup>
</FormGroup>
</Col>
<Col md={3}>
<FormGroup>
<FormLabel className="fw-semibold">Status</FormLabel>
<InputGroup>
<InputGroup.Text className="rounded-4">
<FaCircleInfo />
</InputGroup.Text>
<Form.Select
className="rounded-4"
value={search.idStatus}
onChange={(e) => setSearch(prev => ({ ...prev, idStatus: e.target.value }))}
>
<option value="">Status</option>
{status.slice(10, 14).map((s) => (
<option key={s.idStatus} value={s.idStatus}>
{s.status}
</option>
))}
</Form.Select>
</InputGroup>
</FormGroup>
</Col>
<Col md={3} className="d-flex align-items-end">
<Button type="submit" className="w-100 rounded-5" disabled={isLoading}>
{isLoading ? 'Buscando...' : 'Buscar'}
</Button>
</Col>
</Row>
</Form>
</section>
{/*
<div className="row g-3">
{/* Número de cuenta */}
<div className="col-md-3">
<label className="form-label">Número de Cuenta</label>
<input
@@ -135,7 +208,7 @@ export default function LiberarCasoEspecial({ responsable, imprimirError }: Prop
/>
</div>
{/* Nombre */}
<div className="col-md-3">
<label className="form-label">Nombre</label>
<input
@@ -148,7 +221,6 @@ export default function LiberarCasoEspecial({ responsable, imprimirError }: Prop
/>
</div>
{/* Status */}
<div className="col-md-3">
<label className="form-label">Status</label>
<select
@@ -167,7 +239,7 @@ export default function LiberarCasoEspecial({ responsable, imprimirError }: Prop
</select>
</div>
{/* Botón Buscar */}
<div className="col-md-3 d-flex align-items-end">
<button
className="btn btn-info w-100"
@@ -178,6 +250,17 @@ export default function LiberarCasoEspecial({ responsable, imprimirError }: Prop
</button>
</div>
</div>
*/}
<ServicioSocialTabla
columnaFechaFin={false}
columnaFechaInicio={false}
columnaFechaRegistro={true}
idTipoUsuario={responsable.idTipoUsuario}
total={total}
data={data}
onPageChange={onPageChange}
/>
{/* Tabla */}
{/*
+2
View File
@@ -1,3 +1,4 @@
'use client'
import { useRouter } from "next/navigation";
import { useEffect, useState } from "react";
@@ -14,6 +15,7 @@ export default function Logout() {
const handleLogout = () => {
localStorage.removeItem("token");
localStorage.removeItem('usuario');
localStorage.clear();
router.push("/");
}
@@ -2,6 +2,9 @@
import React, { useEffect, useMemo, useState } from "react";
import { axiosInstance } from "@/api/config";
import { routerServerGlobal } from "next/dist/server/lib/router-utils/router-server-context";
import { useRouter } from "next/navigation";
import Swal from "sweetalert2";
type Responsable = { token?: string };
@@ -44,6 +47,8 @@ export default function CuestionarioResponsbale2({
const [isLoading, setIsLoading] = useState(false);
const [respuestas, setRespuestas] = useState<Respuestas>({});
const router = useRouter()
const formulario: Formulario = useMemo(() => ({
titulo: '2025 Evaluación de la institución receptora de servicio social',
descripcion: `Estimado(a) responsable de programa de servicio social:\nTe solicitamos llenar cuidadosamente los campos solicitados a continuación para poder\nvalidar el término del servicio social de nuestro alumno(a).`,
@@ -153,12 +158,16 @@ export default function CuestionarioResponsbale2({
else answered = answer !== null && String(answer) !== '';
if (!answered) missing.push(`Pregunta ${p.numeroPregunta}`);
});
formulario.tablas.forEach((t) => {
formulario.tablas.forEach((t, index) => {
const allAnswered = t.renglones.every((r) => {
const tablaResp = respuestas[String(t.idTabla)] as Record<number, string | null> | undefined;
return tablaResp && tablaResp[r.idRenglon] !== null;
});
if (!allAnswered) missing.push(`Tabla ${t.numeroPregunta}`);
if (!allAnswered) {
const letra = String.fromCharCode(65 + index);
missing.push(`Tabla ${t.numeroPregunta}.${letra}`);
}
});
return missing;
}, [formulario, respuestas]);
@@ -204,6 +213,7 @@ export default function CuestionarioResponsbale2({
}
}
}
for (const t of formulario.tablas) {
for (const r of t.renglones) {
const tablaResp = respuestas[String(t.idTabla)] as Record<number, string | null> | undefined;
@@ -235,12 +245,32 @@ export default function CuestionarioResponsbale2({
const valor = respuestasTabla[idR as unknown as number];
let clave = `p${numPregunta}_${idR}`;
if (subLetra) clave = `p${numPregunta}_${subLetra}_${idR}`;
resultado[clave] = valor ?? '';
// antes resultado[clave] = valor ?? '';
// Modifique esta linea
if (valor !== null && valor !== '') {
resultado[clave] = valor;
}
}
} else {
const preguntaDef = formulario.preguntas.find((p) => p.id === key);
if (!preguntaDef) continue;
resultado[`p${preguntaDef.numeroPregunta}`] = resps[key] as string;
// VALIDAR CONDICIONAL (p6)
if (preguntaDef.condicional) {
const cond = preguntaDef.condicional;
if (resps[cond.preguntaId] !== cond.valor) {
continue; // NO mandar esta pregunta
}
}
const valor = resps[key];
if (valor !== null && String(valor).trim() !== '') {
resultado[`p${preguntaDef.numeroPregunta}`] = valor as string;
}
//resultado[`p${preguntaDef.numeroPregunta}`] = resps[key] as string;
}
}
return resultado;
@@ -251,21 +281,36 @@ export default function CuestionarioResponsbale2({
updateIsLoading(true);
setIsLoading(true);
try {
const data = { ...formatearRespuestas(respuestas), idServicio: localStorage.getItem('idServicio') };
const data = { ...formatearRespuestas(respuestas), idServicio: Number(localStorage.getItem('idServicio')) };
console.log('Datos a enviar:', data);
const headers = responsable?.token ? { Authorization: `Bearer ${responsable.token}` } : undefined;
await axiosInstance.post('/cuestionario_programa', data, { headers });
imprimirMensaje('Se enviaron los datos correctamente');
try { localStorage.removeItem('idCuestionarioAlumno'); } catch (_) {}
if (typeof window !== 'undefined') window.location.href = '/alumno';
} catch (err: unknown) {
if (typeof err === 'object' && err !== null && 'response' in err) {
const anyErr = err as { response?: { data?: { message?: unknown } } };
imprimirError(String(anyErr.response?.data?.message) || 'Error al enviar el formulario');
} else if (err instanceof Error) {
imprimirError(err.message);
} else {
imprimirError('Error al enviar el formulario');
}
await axiosInstance.post('/cuestionario-programa2', data, { headers });
Swal.fire({
title: 'Éxito',
text: 'Se subio el cuestionario correctamente.',
icon: 'success',
})
//imprimirMensaje('Se enviaron los datos correctamente');
// try { localStorage.removeItem('idCuestionarioAlumno'); } catch (_) {}
if (typeof window !== 'undefined') router.replace('/responsable'); //window.location.href = '/responsable';
} catch (err) {
Swal.fire({
title: 'Error',
text: 'No se pudo enviar el cuestionario.',
icon: 'error',
})
// if (typeof err === 'object' && err !== null && 'response' in err) {
// const anyErr = err as { response?: { data?: { message?: unknown } } };
// imprimirError(String(anyErr.response?.data?.message) || 'Error al enviar el formulario');
// } else if (err instanceof Error) {
// imprimirError(err.message);
// } else {
// imprimirError('Error al enviar el formulario');
// }
} finally {
updateIsLoading(false);
setIsLoading(false);
@@ -291,12 +336,12 @@ export default function CuestionarioResponsbale2({
const p = item as Pregunta;
return (
<div key={p.id} className="mb-6 mt-6">
<label htmlFor={p.id} className="form-label">{p.numeroPregunta}. {p.texto}</label>
<label htmlFor={p.id} className="form-label mb-3">{p.numeroPregunta}. {p.texto}</label>
{p.tipo === 'seleccionUnica' && p.opciones && (
<div>
<div className="mb-2">
{p.opciones.map((op) => (
<div key={op} className="SINO">
<label>
<label className="mb-3">
<input type="radio" name={p.id} checked={respuestas[p.id] === op} onChange={() => setSingle(p.id, op)} /> {op}
</label>
</div>
@@ -316,7 +361,7 @@ export default function CuestionarioResponsbale2({
)}
{p.tipo === 'texto' && (
<div>
<textarea id={p.id} maxLength={p.limite} placeholder="Escribe tu respuesta aquí" value={String(respuestas[p.id] ?? '')} onChange={(e) => setSingle(p.id, e.target.value)} className="form-control" />
<textarea id={p.id} maxLength={p.limite} placeholder="Escribe tu respuesta aquí" value={String(respuestas[p.id] ?? '')} onChange={(e) => setSingle(p.id, e.target.value)} className="form-control mb-3 bg-transparent" />
</div>
)}
</div>
@@ -326,22 +371,22 @@ export default function CuestionarioResponsbale2({
return (
<div key={t.idTabla} className="mb-6 table-responsive">
{(index === 0 || t.numeroPregunta !== (sortedItems[index - 1] as Tabla).numeroPregunta) && (
<h3 className="mb-3">{t.numeroPregunta}. {t.preguntaTabla}</h3>
<h5 className="mb-3 mt-4">{t.numeroPregunta}. {t.preguntaTabla}</h5>
)}
{t.subPreguntaTabla && <h5 className="mb-3">{t.subPreguntaTabla}</h5>}
<table className="table table-bordered text-center">
<thead>
<tr>
<th></th>
{(t.renglones[0].opciones || []).map((op) => <th key={op}>{op}</th>)}
<th className="bg-transparent"></th>
{(t.renglones[0].opciones || []).map((op) => <th key={op} className="bg-transparent">{op}</th>)}
</tr>
</thead>
<tbody>
{t.renglones.map((r) => (
<tr key={r.idRenglon}>
<td>{r.textoRenglon}</td>
<td className="bg-transparent">{r.textoRenglon}</td>
{(r.opciones || []).map((op) => (
<td key={op}>
<td key={op} className="bg-transparent">
<input type="radio" name={`tabla-${t.idTabla}-renglon-${r.idRenglon}`} value={op} checked={((respuestas[String(t.idTabla)] as Record<number, string | null>) || {})[r.idRenglon] === op} onChange={() => setTableAnswer(t.idTabla, r.idRenglon, op)} />
</td>
))}
+226 -75
View File
@@ -1,12 +1,19 @@
"use client";
import React, { useEffect, useState } from "react";
import { useEffect, useState } from "react";
import { axiosInstance } from "@/api/config";
import type { AxiosError } from "axios";
import { Button, Col, Form, FormGroup, FormLabel, InputGroup } from "react-bootstrap";
import { FaRegCalendarAlt, FaSchool, FaUpload } from "react-icons/fa";
import { Col, FormGroup, FormLabel, InputGroup } from "react-bootstrap";
import { FaRegCalendar, FaUpload } from "react-icons/fa6";
import DatePicker from "react-datepicker";
import { PiDotFill } from "react-icons/pi";
import axios from "axios";
import Image from "next/image";
import Swal from "sweetalert2";
import { registerLocale } from "react-datepicker";
import { es } from "date-fns/locale/es";
registerLocale("es", es);
type Responsable = {
idUsuario?: number;
@@ -60,6 +67,16 @@ export default function NuevoServicio({
const [minDate2, setMinDate2] = useState<Date>(new Date());
const [programa, setPrograma] = useState<Programa>({});
const handleFechaInicioChange = (date: Date | null) => {
// setFechaInicio(date || new Date());
if (date) setFechaInicio(date);
}
const handleFechaFinChange = (date: Date | null) => {
// setFechaFin(date || new Date());
if (date) setFechaFin(date);
}
const resetar = () => {
setCorreo("");
setProfesor("");
@@ -71,6 +88,17 @@ export default function NuevoServicio({
setPrograma({});
};
const confirmarServicioS = () => {
Swal.fire({
title: 'Warning',
text: '¿Esta seguro(a) de querer Pre-Registrar a este alumno?',
icon: 'warning',
confirmButtonText: 'Confirmar',
cancelButtonText: 'Cancelar',
})
}
// Validacion para mostrar el boton
const mostrarBoton = () => {
if (!alumno.idUsuario) return true;
@@ -86,18 +114,29 @@ export default function NuevoServicio({
updateIsLoading(true);
try {
const headers = responsable?.token ? { Authorization: `Bearer ${responsable.token}` } : undefined;
const res = await axiosInstance.get<Alumno>(`/usuario/escolares?numeroCuenta=${encodeURIComponent(numeroCuenta)}`, { headers });
// Enviar headers como tercer parámetro (config), no como body
const res = await axiosInstance.post<Alumno>(`/usuario/escolares/${numeroCuenta}`);
resetar();
setAlumno(res.data);
obtenerProgramas();
updateIsLoading(false);
} catch (err: unknown) {
setNumeroCuenta("");
resetar();
updateIsLoading(false);
const axiosErr = err as AxiosError;
if (axiosErr?.response?.data) imprimirError(axiosErr.response.data as object);
else if (err instanceof Error) imprimirError(err.message);
else imprimirError({ message: "Error desconocido" });
const axiosErr = err as AxiosError<any>;
const mensaje = axiosErr?.response?.data?.message || 'Ocurrio un error inesperado';
console.log("mensaje de error", mensaje)
Swal.fire({
icon: 'warning',
title: 'Aviso',
text: mensaje,
})
// const axiosErr = err as AxiosError;
// if (axiosErr?.response?.data) imprimirError(axiosErr.response.data as object);
// else if (err instanceof Error) imprimirError(err.message);
// else imprimirError({ message: "Error desconocido" });
}
};
@@ -114,35 +153,76 @@ export default function NuevoServicio({
correo,
programaInterno,
profesor,
fechaInicio: fechaInicio.toISOString(),
fechaFin: fechaFin.toISOString(),
fechaInicio: formatDate(fechaInicio),
fechaFin: formatDate(fechaFin),
} as const;
formData.append("alumno", JSON.stringify(data));
if (file) formData.append("cartaAceptacion", file, file.name);
if (!file) {
imprimirError({ message: 'No hay archivo seleccionado' });
return;
}
// Para verificar que se envie el archivo
console.log("Archivo a enviar:", file);
//if (file)
formData.append("cartaAceptacion", file, file.name);
updateIsLoading(true);
try {
const headers = responsable?.tokenArchivo ? { Authorization: `Bearer ${responsable.tokenArchivo}`, 'Content-Type': 'multipart/form-data' } : { 'Content-Type': 'multipart/form-data' };
const res = await axiosInstance.post("/servicio/nuevo", formData, { headers });
const headers = responsable?.tokenArchivo ? { Authorization: `Bearer ${responsable.tokenArchivo}` }: {};
// Falta poner la variable de entorno
const res = await axios.post(`${process.env.NEXT_PUBLIC_API_URL}/servicio/nuevo`, formData, {
headers: {
...(responsable?.tokenArchivo && {
Authorization: `Bearer ${responsable.tokenArchivo}`,
}),
},
});
setNumeroCuenta("");
resetar();
updateIsLoading(false);
imprimirMensaje(res.data?.message || "Registrado");
Swal.fire('Exito', 'El alumno se registro en el servicio social', 'success')
// imprimirMensaje(res.data?.message || "Registrado");
resetar();
} catch (err: unknown) {
updateIsLoading(false);
const axiosErr = err as AxiosError;
if (axiosErr?.response?.data) imprimirError(axiosErr.response.data as object);
else if (err instanceof Error) imprimirError(err.message);
else imprimirError({ message: "Error desconocido" });
const axiosErr = err as AxiosError<any>;
const mensaje = axiosErr?.response?.data?.message || 'No se pudo registrar al alumno';
Swal.fire({
icon: 'error',
title: 'Error',
text: mensaje,
})
// const axiosErr = err as AxiosError;
// if (axiosErr?.response?.data) imprimirError(axiosErr.response.data as object);
// else if (err instanceof Error) imprimirError(err.message);
// else imprimirError({ message: "Error desconocido" });
}
};
const obtenerProgramas = async () => {
try {
const headers = responsable?.token ? { Authorization: `Bearer ${responsable.token}` } : undefined;
const res = await axiosInstance.get<Programa[]>(`/programa/programas_responsable?idUsuario=${responsable.idUsuario}`, { headers });
setProgramas(res.data);
const res = await axiosInstance.get(`/programa/programas_responsable/${responsable.idUsuario}`, { headers });
// Normalizar la respuesta: soportar varias formas comunes:
// - res.data = Array
// - res.data = { data: Array }
// - res.data = { programas: Array }
const payload = res?.data ?? res;
console.log("Esta es la info que traen los programas", payload);
if (Array.isArray(payload)) {
setProgramas(payload as Programa[]);
} else if (payload && Array.isArray((payload as any).data)) {
setProgramas((payload as any).data as Programa[]);
} else if (payload && Array.isArray((payload as any).programas)) {
setProgramas((payload as any).programas as Programa[]);
} else {
console.warn('Respuesta inesperada al obtener programas, se usará lista vacía', payload);
setProgramas([]);
}
} catch (err: unknown) {
const axiosErr = err as AxiosError;
if (axiosErr?.response?.data) imprimirError(axiosErr.response.data as object);
@@ -174,86 +254,135 @@ export default function NuevoServicio({
md.setDate(md.getDate() - 16);
setMinDate(md);
updateFechas();
//obtenerProgramas();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [responsable.idTipoUsuario]);
// Fetch programas when responsable id is available
useEffect(() => {
if (responsable?.idUsuario) {
obtenerProgramas();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
}, [responsable?.idUsuario]);
useEffect(() => {
updateFechas();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [fechaInicio]);
const formatDate = (date: Date) => {
return date.toISOString().split("T")[0];
};
return (
<div>
<div className="mb-4">
<label className="form-label">Número de Cuenta</label>
<div className="d-flex gap-2">
<label className="form-label fw-bolder">Número de Cuenta</label>
<div className="d-flex">
<InputGroup.Text>
<FaSchool />
<Image src="/image/numCuenta.svg" width={24} height={24} alt="Icono de escuela" />
</InputGroup.Text>
<input
type="text"
placeholder="Número de Cuenta"
maxLength={9}
value={numeroCuenta}
value={numeroCuenta ?? ''}
onChange={(e) => setNumeroCuenta(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && buscarAlumno()}
className="form-control"
/>
<button className="" onClick={() => buscarAlumno()}>Buscar</button>
<button type="button" className="" onClick={() => buscarAlumno()}>Buscar</button>
</div>
</div>
<div className="mb-3">
<label className="form-label">Nombre</label>
<p className="form-control">{alumno.nombre}</p>
</div>
{alumno.nombre && (
<div className="mb-3">
<label className="form-label fw-bolder">Nombre</label>
<p className="form-control">{alumno.nombre}</p>
{/* <input className="form-control" value={alumno.nombre} disabled/> */}
</div>
)}
{alumno.carrera && (
<div className="mb-3">
<label className="form-label fw-bolder">Carrera</label>
{/* <input className="form-control" value={alumno.carrera ? String(alumno.carrera) : ''} disabled/> */}
<p className="form-control">{alumno.carrera ? String(alumno.carrera) : ''}</p>
</div>
)}
{alumno.creditos && (
<div className="mb-3">
<label className="form-label fw-bolder">Créditos</label>
{/* <input className="form-control" value={alumno.creditos ? parseInt(String(alumno.creditos)) : '' + '%'} disabled/> */}
<p className="form-control">{alumno.creditos ? parseInt(String(alumno.creditos)) : ''}{alumno.creditos ? '%' : ''}</p>
</div>
)}
<div className="mb-3">
<label className="form-label">Carrera</label>
<p className="form-control">{alumno.carrera ? String(alumno.carrera) : ''}</p>
</div>
<div className="mb-3">
<label className="form-label">Créditos</label>
<p className="form-control">{alumno.creditos ? parseInt(String(alumno.creditos)) : ''}{alumno.creditos ? '%' : ''}</p>
</div>
<div className="mb-3">
<label className="form-label">Seleccione programa</label>
<select className="form-select" value={JSON.stringify(programa) } onChange={(e) => { try { setPrograma(JSON.parse(e.target.value)); } catch { setPrograma({}); } }}>
<option value={JSON.stringify({})} disabled>Seleccionar</option>
{programas.map((p, i) => (
<option key={i} value={JSON.stringify(p)}>{p.programa}</option>
<label className="form-label fw-bolder">Seleccione programa</label>
<select className="form-select"
value={programa.idPrograma ?? ''}
onChange={(e) => {
const prog = programas.find(p => p.idPrograma === Number(e.target.value));
setPrograma(prog ?? {})
}}
>
<option value={''} disabled>Seleccionar</option>
{programas.map(p => (
<option key={p.idPrograma} value={p.idPrograma}>
{p.programa}
</option>
))}
</select>
</div>
<div className="mb-3">
<label className="form-label">Institución</label>
<p className="form-control">{programa.institucion}</p>
</div>
{/* <div className="mb-3">
<label className="form-label fw-bolder">Seleccione programa</label>
<select className="form-select" value={JSON.stringify(programa) } onChange={(e) => { try { setPrograma(JSON.parse(e.target.value)); } catch { setPrograma({}); } }}>
<option value={JSON.stringify({})} disabled>Seleccionar</option>
{(!Array.isArray(programas) || programas.length === 0) && (
<option value={JSON.stringify({})} disabled>No hay programas disponibles</option>
)}
{Array.isArray(programas) && programas.length > 0 && programas.map((p, i) => (
<option key={i} value={JSON.stringify(p)}>{p.programa}</option>
))}
</select>
</div> */}
<div className="mb-3">
<label className="form-label">Dependencia</label>
<p className="form-control">{programa.dependencia}</p>
</div>
{programa.institucion && (
<div className="mb-3">
<label className="form-label fw-bolder">Institución</label>
<p className="form-control">{programa.institucion}</p>
</div>
)}
<div className="mb-3">
<label className="form-label">Clave del programa</label>
<p className="form-control">{programa.clavePrograma}</p>
</div>
{programa.dependencia && (
<div className="mb-3">
<label className="form-label fw-bolder">Dependencia</label>
<p className="form-control">{programa.dependencia}</p>
</div>
)}
{programa.clavePrograma && (
<div className="mb-3">
<label className="form-label fw-bolder">Clave del programa</label>
<p className="form-control">{programa.clavePrograma}</p>
</div>
)}
{programa.acatlan && (
<>
<div className="mb-3">
<label className="form-label">Programa interno</label>
<label className="form-label fw-bolder">Programa interno</label>
<input className="form-control" placeholder="Programa interno" value={programaInterno} onChange={(e) => setProgramaInterno(e.target.value)} />
</div>
<div className="mb-3">
<label className="form-label">Profesor/Responsable de Programa</label>
<label className="form-label fw-bolder">Profesor/Responsable de Programa</label>
<input className="form-control" placeholder="Profesor/Responsable de Programa" value={profesor} onChange={(e) => setProfesor(e.target.value)} />
</div>
</>
@@ -309,19 +438,28 @@ export default function NuevoServicio({
<Col>
<FormGroup>
<FormLabel>Fecha de inicio</FormLabel>
<FormLabel className="fw-bolder">Fecha de inicio</FormLabel>
<InputGroup>
<InputGroup.Text>
<FaRegCalendarAlt />
<FaRegCalendar />
</InputGroup.Text>
<DatePicker
selected={fechaInicio}
onChange={handleFechaInicioChange}
minDate={minDate}
dateFormat="dd-MM-yyyy"
className="form-control"
wrapperClassName="flex-grow-1"
calendarClassName="mi-calendario"
showMonthDropdown
showYearDropdown
scrollableYearDropdown
yearDropdownItemNumber={15}
dropdownMode="select"
locale="es"
/>
</InputGroup>
</FormGroup>
@@ -329,26 +467,36 @@ export default function NuevoServicio({
<Col>
<FormGroup>
<FormLabel>Fecha de fin</FormLabel>
<FormLabel className="fw-bolder">Fecha de fin</FormLabel>
<InputGroup>
<InputGroup.Text>
<FaRegCalendarAlt />
<FaRegCalendar />
</InputGroup.Text>
<DatePicker
selected={fechaFin}
value={fechaFin.toISOString().substring(0,10)}
onChange={handleFechaFinChange}
// value={fechaFin.toISOString().substring(0,10)}
minDate={minDate2}
dateFormat="dd-MM-yyyy"
className="form-control"
wrapperClassName="flex-grow-1"
calendarClassName="mi-calendario"
showMonthDropdown
showYearDropdown
scrollableYearDropdown
yearDropdownItemNumber={15}
dropdownMode="select"
locale="es"
/>
</InputGroup>
</FormGroup>
</Col>
<div className="mb-3">
{/*
<div className="mb-3">
<label className="form-label">Fecha de inicio</label>
<input type="date" className="form-control" value={fechaInicio.toISOString().substring(0,10)} min={minDate.toISOString().substring(0,10)} onChange={(e) => setFechaInicio(new Date(e.target.value))} />
</div>
@@ -359,24 +507,25 @@ export default function NuevoServicio({
<input type="date" className="form-control" value={fechaFin.toISOString().substring(0,10)} min={minDate2.toISOString().substring(0,10)} onChange={(e) => setFechaFin(new Date(e.target.value))} />
</div>
)}
*/}
<div className="mb-3">
<label className="form-label">Correo electrónico del Alumno</label>
<label className="form-label fw-bolder">Correo electrónico del Alumno</label>
<input type="email" className="form-control" placeholder="Email" value={correo} onChange={(e) => setCorreo(e.target.value)} />
</div>
<FormGroup className="mb-3">
<FormLabel>Carta de aceptación (en formato .PDF. No se aceptan fotos).</FormLabel>
<div className="border p-4 text-center rounded" style={{ cursor: "pointer" }} onClick={() => document.getElementById("fileInput")?.click()}>
<FormLabel className="fw-bolder">Carta de aceptación (en formato .PDF. No se aceptan fotos).</FormLabel>
<div className="border p-4 text-center rounded" style={{ cursor: "pointer" }} onClick={() => document.getElementById("pdfInput")?.click()}>
<FaUpload size={40} className="mb-2"/>
<p className="mb-1">
{file?.name || 'Arrastra aquí tu archivo o da click aquí para buscar'}
</p>
<p className="is-size-6">Tamaño máximo 200</p>
<p className="is-size-6">Tamaño máximo 20MB</p>
<p className="is-size-7">Si al momento de elegir un archivo este no se selecciona, haga click en cancelar en la ventana emergente e intente de nuevo.</p>
</div>
<input
id="fileInput"
id="pdfInput"
type="file"
style={{ display: 'none'}}
accept="application/pdf"
@@ -384,7 +533,8 @@ export default function NuevoServicio({
/>
</FormGroup>
<div className="mb-3">
{/*
<div className="mb-3">
<label className="form-label">Carta de aceptación (en formato .PDF. No se aceptan fotos).</label>
<input type="file" accept="application/pdf" className="form-control" onChange={(e) => onFileChange(e.target.files?.[0])} />
<div className="mt-2 text-muted">
@@ -393,9 +543,10 @@ export default function NuevoServicio({
<p>Si al momento de elegir un archivo este no se selecciona, haga click en cancelar en la ventana emergente e intente de nuevo.</p>
</div>
</div>
*/}
<div className="has-text-centered mt-4">
<button disabled={mostrarBoton()} onClick={() => imprimirWarning('¿Esta seguro(a) de querer Pre-Registrar a este alumno?', preRegistrar)}>
<div className="text-center mt-4 mb-3">
<button className="btn btn-success" disabled={mostrarBoton()} onClick={() => imprimirWarning('¿Esta seguro(a) de querer Pre-Registrar a este alumno?', preRegistrar)}>
Enviar archivo
</button>
</div>
@@ -1,13 +1,12 @@
"use client";
import React, { useEffect, useState } from "react";
import { useEffect, useState } from "react";
import { axiosInstance } from "@/api/config";
import type { AxiosError } from "axios";
import { ServicioSocialResponse } from "@/types/responses";
import ServicioSocialTabla from "../servicio-social-tabla";
import { Button, Col, FormControl, FormGroup, FormLabel, FormSelect, InputGroup, Row } from "react-bootstrap";
import { FaClipboardList, FaInfoCircle, FaSchool, FaUser } from "react-icons/fa";
import { Prev } from "react-bootstrap/esm/PageItem";
import { FaCircleInfo, FaUser } from "react-icons/fa6";
import Image from "next/image";
import { useRouter } from "next/navigation";
type Responsable = { idUsuario?: number; token?: string; idTipoUsuario?: number };
type StatusItem = { idStatus: number; status: string };
@@ -23,14 +22,26 @@ export default function TablaServiciosResponsable({ responsable, imprimirError }
const [total, setTotal] = useState(0);
const [data, setData] = useState<ServicioSocialResponse[]>([]);
const [status, setStatus] = useState<StatusItem[]>([]);
const [search, setSearch] = useState<{ numeroCuenta?: string; nombre?: string; idStatus?: string }>({ idStatus: '' });
const [search, setSearch] = useState<{ numeroCuenta?: string; nombre?: string; idStatus?: string }>({ numeroCuenta: '', nombre: '', idStatus: '' });
const [searchAnterior, setSearchAnterior] = useState<{ numeroCuenta?: string; nombre?: string; idStatus?: string }>({});
const [mounted, setMounted] = useState(false);
const router = useRouter();
const onPageChange = (p: number) => {
setPage(p);
obtenerServicios(p);
};
const onRowAction = (item: ServicioSocialResponse, action: string) => {
if (action === "carta_aceptacion") {
localStorage.setItem("idServicio", String(item.idServicio));
router.push("/responsable/carta_aceptacion");
} else if (action === "carta_termino") {
localStorage.setItem("idServicio", String(item.idServicio));
router.push("/responsable/carta_termino");
}
};
const obtenerServicios = async (pagina = page) => {
let query = "";
setIsLoading(true);
@@ -49,14 +60,21 @@ export default function TablaServiciosResponsable({ responsable, imprimirError }
if (search.nombre) query += `&nombre=${encodeURIComponent(search.nombre)}`;
if (search.numeroCuenta) query += `&numeroCuenta=${encodeURIComponent(search.numeroCuenta)}`;
const idUsuario = localStorage.getItem("idUsuario")
try {
console.log('Informacion en el componente hijo del responsable:', responsable);
console.log('Id del usuario responsable:', responsable.idUsuario);
const idUsuario = localStorage.getItem('idUsuario') ?? '';
const idUsuario1 = localStorage.getItem('idUsuario') ?? '';
console.log('idUsuario usado en la consulta componente hijo:', idUsuario);
console.log("Este es la optencion del id del usuario desde el local", idUsuario1)
//const headers = responsable?.token ? { Authorization: `Bearer ${responsable.token}` } : undefined;
const res = await axiosInstance.get<{ serviciosResponsable: ServicioSocialResponse[]; count: number }>(`/servicio/servicios_responsable?idUsuario=${idUsuario}&pagina=${pagina}${query}`);
//const res = await axiosInstance.get<{ serviciosResponsable: ServicioSocialResponse[]; count: number }>(`/servicio/servicios_responsable?idUsuario=${idUsuario}&pagina=${pagina}${query}`);
const res = await axiosInstance.get(`/servicio/servicios_responsable?idUsuario=${idUsuario1}&pagina=${pagina}${query}`);
//const res = await axiosInstance.get(`/servicio/servicios_responsable?idUsuario=${idUsuario1}&pagina=${pagina}${query}`);
setData(res.data.serviciosResponsable);
setTotal(res.data.count);
setIsLoading(false);
@@ -87,6 +105,8 @@ export default function TablaServiciosResponsable({ responsable, imprimirError }
useEffect(() => {
obtenerCatalogoStatus();
// mark as mounted to avoid rendering dynamic options during SSR hydration
setMounted(true);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
@@ -99,7 +119,7 @@ export default function TablaServiciosResponsable({ responsable, imprimirError }
<FormLabel>Número de cuenta</FormLabel>
<InputGroup>
<InputGroup.Text className="rounded-4">
<FaSchool />
<Image src="/image/numCuenta.svg" width={24} height={24} alt="Icono de escuela" />
</InputGroup.Text>
<FormControl
type="text"
@@ -133,21 +153,23 @@ export default function TablaServiciosResponsable({ responsable, imprimirError }
</InputGroup>
</FormGroup>
</Col>
{/* llega hasta el 10 */}
{/* llega hasta el 10 */}
<Col md={3}>
<FormGroup>
<FormLabel>Status</FormLabel>
<InputGroup>
<InputGroup.Text className="rounded-4">
<FaInfoCircle />
<FaCircleInfo />
</InputGroup.Text>
<FormSelect
suppressHydrationWarning
value={search.idStatus}
onChange={(e) => setSearch(Prev => ({ ...Prev, idStatus: e.target.value}))}
className="rounded-4"
onKeyDown={(e) => {if (e.key === 'Enter') obtenerServicios()}}
>
<option value="">Status</option>
{status.slice(0, 9).map((s) => (
{mounted && status.slice(0, 9).map((s) => (
<option key={s.idStatus} value={s.idStatus}>
{s.status}
</option>
@@ -158,10 +180,13 @@ export default function TablaServiciosResponsable({ responsable, imprimirError }
</Col>
<Col md={3} className="d-flex align-items-end">
<Button
type="button"
<Button
type="submit"
className="w-100 rounded-5"
disabled={isLoading}>{isLoading ? 'Buscando...' : 'Buscar'}
disabled={isLoading}
onClick={() => obtenerServicios(1)}
>
{isLoading ? 'Buscando...' : 'Buscar'}
</Button>
</Col>
</Row>
@@ -184,6 +209,7 @@ export default function TablaServiciosResponsable({ responsable, imprimirError }
columnasResponsable={true}
columnaCuestionario={true}
columnaCartaTermino={true}
onRowAction={onRowAction}
/>
</div>
);
+93 -17
View File
@@ -1,8 +1,11 @@
"use client";
import React, { useEffect, useState } from "react";
import { axiosInstance } from "@/api/config";
import type { AxiosError } from "axios";
import { useEffect, useState } from "react";
import { Form } from "react-bootstrap";
import { FaUpload } from "react-icons/fa6";
import { useRouter } from "next/navigation";
import axios from "axios";
import Swal from "sweetalert2";
type Responsable = { tokenArchivo?: string };
@@ -31,6 +34,26 @@ export default function UploadArchivo({
}: Props) {
const [file, setFile] = useState<File | null>(null);
const router = useRouter();
console.log("Responsable en upload archivo:", responsable);
const confirmarEnvioArchivo = () => {
Swal.fire({
title: `¿Estas seguro(a) de querer subir esta carta de ${tipoCarta}?`,
icon: "warning",
showCancelButton: true,
confirmButtonText: "Sí, actualizar",
cancelButtonText: "Cancelar",
confirmButtonColor: "#0d6efd",
cancelButtonColor: "#dc3545",
}).then((result) => {
if (result.isConfirmed) {
subir(); // 👈 aquí ejecutas la función
}
});
};
const subir = async () => {
if (!file) return;
const formData = new FormData();
@@ -38,20 +61,39 @@ export default function UploadArchivo({
formData.append('data', JSON.stringify(data));
formData.append(variableName, file, file.name);
console.log("Esta es la info que se mandara al backend:", formData);
updateIsLoading(true);
try {
const headers = responsable?.tokenArchivo ? { Authorization: `Bearer ${responsable.tokenArchivo}`, 'Content-Type': 'multipart/form-data' } : { 'Content-Type': 'multipart/form-data' };
const res = await axiosInstance.put(`/servicio/${path}`, formData, { headers });
try { localStorage.removeItem('idServicio'); } catch (error) {}
updateIsLoading(false);
imprimirMensaje(res.data?.message || 'Archivo subido');
if (typeof window !== 'undefined') window.location.href = '/responsable';
} catch (err: unknown) {
updateIsLoading(false);
const axiosErr = err as AxiosError;
if (axiosErr?.response?.data) imprimirError(axiosErr.response.data as object);
else if (err instanceof Error) imprimirError(err.message);
else imprimirError({ message: 'Error desconocido' });
// Cambiamos a la direccion de axios normal
const res = await axios.put(`${process.env.NEXT_PUBLIC_API_URL}/servicio/${path}`, formData, {
headers: {
Authorization: `Bearer ${responsable.tokenArchivo}`,
'Content-Type': 'multipart/form-data',
},
});
try { localStorage.removeItem('idServicio'); }
catch (error) {}
updateIsLoading(false);
Swal.fire({
title: 'Éxito',
text: 'Se subio el archivo correctamente',
icon: 'success',
})
//imprimirMensaje(res.data?.message || 'Archivo subido');
if (typeof window !== 'undefined') router.replace('/responsable') //window.location.href = '/responsable';
} catch (err) {
Swal.fire({
title: 'Error',
text: 'Error al enviar el archivo',
icon: 'error',
})
updateIsLoading(false);
// const axiosErr = err as AxiosError;
// if (axiosErr?.response?.data) imprimirError(axiosErr.response.data as object);
// else if (err instanceof Error) imprimirError(err.message);
// else imprimirError({ message: 'Error desconocido' });
}
};
@@ -65,20 +107,54 @@ export default function UploadArchivo({
return (
<div>
<div className="mb-3">
{/* <div className="mb-3">
<label className="form-label">Carta de {tipoCarta} (en formato .pdf, no se aceptan fotos).</label>
<input type="file" accept="application/pdf" className="form-control" onChange={(e) => setFile(e.target.files?.[0] ?? null)} />
<div className="mt-2 text-center">
<p className="h5">{file?.name || 'Arrastra aquí tu archivo o da click aquí para buscarlo.'}</p>
<p className="small">Tamaño máximo 20MB</p>
</div>
</div> */}
{/*
Falta corregir y arreglar
*/}
<div className="ph-5">
<Form.Group className="mb-3">
<Form.Label>
Carta de {tipoCarta} (en formato .pdf, no se aceptan fotos).
</Form.Label>
<div
className="border p-4 text-center rounded"
style={{ cursor: 'pointer' }}
onClick={() => document.getElementById('fileInput')?.click()}
>
<FaUpload size={40} className="mb-2"/>
<p className="mb-1">
{file?.name || 'Arrastra aquí tu archivo o da click aquí para buscar'}
</p>
<p className="is-size-6">Tamaño máximo 20MB</p>
<p className="is-size-7">Si al momento de elegir un archivo este no se selecciona, haga click en cancelar en la ventana emergente e intente de nuevo.
</p>
<input
type="file"
accept="application/pdf"
id="fileInput"
className="d-none"
onChange={(e) => setFile(e.target.files?.[0] ?? null)} />
</div>
</Form.Group>
</div>
<div className="text-center my-4">
<button className="btn btn-success" disabled={!file?.name} onClick={() => imprimirWarning(`¿Estas seguro(a) de querer subir esta carta de ${tipoCarta}?`, subir)}>
<button className="btn btn-success" disabled={!file?.name} onClick={confirmarEnvioArchivo}>
Enviar
</button>
</div>
</div>
);
}
+118 -41
View File
@@ -46,25 +46,49 @@ export default function ServicioSocialTabla({
// 🔹 Cargar datos del padre si existen
useEffect(() => {
if (data && data.length > 0) {
setInfo(data);
setInfo(data);
}
}, [data]);
// 🔹 Paginación simple
function handlePrev() {
if (currentPage > 1) {
const np = currentPage - 1;
setCurrentPage(np);
onPageChange?.(np);
const np = currentPage - 1;
setCurrentPage(np);
onPageChange?.(np);
}
}
function handleNext() {
const lastPage = Math.max(1, Math.ceil(total / 10));
if (currentPage < lastPage) {
const np = currentPage + 1;
setCurrentPage(np);
onPageChange?.(np);
const np = currentPage + 1;
setCurrentPage(np);
onPageChange?.(np);
}
}
const servicioSelectedResponsable = (row: ServicioSocialResponse | ServicioSocialConCasoEspecial) => {
if (!row) return;
if (idTipoUsuario === 2) {
console.log("Responsable seleccionó un servicio social:", row);
if (row.idServicio) {
localStorage.setItem("idServicio", String(row.idServicio));
}
router.push("/responsable/cuestionario");
}
}
const servicioSelectedResponsableCartaTermino = (row: ServicioSocialResponse | ServicioSocialConCasoEspecial) => {
if (!row) return;
if (idTipoUsuario === 2) {
console.log("Responsable seleccionó un servicio social para carta de termino:", row);
if (row.idServicio) {
localStorage.setItem("idServicio", String(row.idServicio));
}
router.push("/responsable/carta_termino");
}
}
@@ -77,12 +101,22 @@ export default function ServicioSocialTabla({
localStorage.setItem("idServicio", String(row.idServicio));
}
// esta mal esta logica
if ("idCasoEspecial" in row && row.idCasoEspecial) {
localStorage.setItem("idCasoEspecial", String(row.idCasoEspecial));
}
router.push("/administrador/servicio");
if ( row.idServicio ) {
router.push("/administrador/servicio");
} else if ("idCasoEspecial" in row && row.idCasoEspecial) {
router.push('/administrador/casos_especiales/caso_especial')
}
// Hacer validacion para saber que id fue y redireccionar
//router.push("/administrador/servicio");
//router.push('/administrador/casos_especiales/caso_especial')
} else if (idTipoUsuario === 2) {
servicioSelectedResponsable(row);
} else if (
row.idServicio ||
("idCasoEspecial" in row && row.idCasoEspecial)
@@ -145,19 +179,19 @@ const servicioSelected = (row: ServicioSocialResponse | ServicioSocialConCasoEsp
) : (
<>
<div className="table-responsive">
<table className="table table-striped">
<table className="table table-striped table-fixed">
<thead>
<tr>
{columnaFechaRegistro && <th>Fecha Registro</th>}
<th>Número de Cuenta</th>
<th>Nombre</th>
<th>Carrera</th>
{columnaFechaInicio && <th>Fecha Inicio</th>}
{columnaFechaFin && <th>Fecha Fin</th>}
<th>Status</th>
{columnaCuestionarioCompleto && <th>Cuestionario Completo</th>}
{columnaCartaTermino && <th>Carta Termino</th>}
{columnaCuestionario && <th>Cuestionario</th>}
{columnaFechaRegistro && <th className="text-center align-middle">Fecha Registro</th>}
<th className="text-center align-middle">Número de Cuenta</th>
<th className="text-center align-middle">Nombre</th>
<th className="text-center align-middle">Carrera</th>
{columnaFechaInicio && <th className="text-center align-middle">Fecha Inicio</th>}
{columnaFechaFin && <th className="text-center align-middle">Fecha Fin</th>}
<th className="text-center align-middle">Status</th>
{columnaCuestionarioCompleto && <th className="text-center align-middle">Cuestionario Completo</th>}
{columnaCartaTermino && <th className="text-center align-middle">Carta Termino</th>}
{columnaCuestionario && <th className="text-center align-middle">Cuestionario</th>}
</tr>
</thead>
<tbody>
@@ -174,17 +208,24 @@ const servicioSelected = (row: ServicioSocialResponse | ServicioSocialConCasoEsp
//idTipoUsuario === 1 ? servicioSelected(item) : undefined
//}
>
<td>{item.Usuario?.usuario}</td>
<td>{item.Usuario?.nombre}</td>
<td>{item.Carrera?.carrera}</td>
{columnaFechaRegistro && <td className="text-center align-middle">{formatDate(item.createdAt)}</td>}
<td className="text-center align-middle">{item.usuario?.usuario}</td>
<td className="text-center align-middle">{item.usuario?.nombre}</td>
<td className="text-center align-middle">{item.carrera?.carrera}</td>
{/*
Poner si quieres hacer condicional para no mostrar infromacion
? formatDate(item.fechaInicio) : "-" */}
{columnaFechaInicio && (
<td>{formatDate(item.fecha_inicio)}</td>
<td className="text-center align-middle">{formatDate(item.fechaInicio)}</td>
)}
{columnaFechaFin && <td>{formatDate(item.fecha_fin)}</td>}
<td>
{columnasResponsable && item.Status?.idStatus === 7 ? (
{columnaFechaFin && <td className="text-center align-middle">{formatDate(item.fechaFin)}</td>}
<td className="text-center align-middle">
{columnasResponsable && item.status?.idStatus === 7 ? (
<button
className="btn btn-sm btn-danger"
className="badge bg-danger border-0"
onClick={() =>
onRowAction?.(item, "carta_aceptacion")
}
@@ -194,36 +235,71 @@ const servicioSelected = (row: ServicioSocialResponse | ServicioSocialConCasoEsp
) : (
<span
className={`badge bg-${statusClass(
item.Status?.idStatus
item.status?.idStatus
)}`}
>
{item.Status?.status}
{item.status?.status}
</span>
)}
</td>
{columnaCartaTermino && (
<td>{item.cartaTermino ? (
<span className="badge bg-success">Completado</span>
) : (
<span className="badge bg-danger">No Completado</span>
)}</td>
<td className="text-center align-middle"
style={{ cursor: (idTipoUsuario === 2 && (item.status?.idStatus === 4 || item.status?.idStatus === 8) && !item.cartaTermino) ? 'pointer' : 'default'}}
onClick={() => {
if (idTipoUsuario === 2 && item.status?.idStatus === 8) {
onRowAction?.(item, "carta_termino");
} else if (idTipoUsuario === 2 && item.status?.idStatus === 4 && !item.cartaTermino) {
servicioSelectedResponsableCartaTermino(item);
}
}}
>
{item.status?.idStatus === 8 ? (
<button
className="badge bg-danger border-0"
onClick={(e) => {
e.stopPropagation();
onRowAction?.(item, "carta_termino");
}}
>
Carta Termino Rechazada
</button>
) : item.status?.idStatus === 4 ? (
item.cartaTermino ? (
<span className="badge"></span>
) : (
<span className="badge text-morado bg-light-morado">Carta Termino</span>
)
) : (
<span className="badge text-secondary"> </span>
)}
</td>
)}
{columnaCuestionarioCompleto && (
<td>{item.cuestionarioCompletado ? (
<td className="text-center align-middle"
>{item.cuestionarioCompletado ? (
<span className="badge bg-success">Completado</span>
) : (
<span className="badge bg-danger">No Completado</span>
<span className="badge text-morado bg-light-morado">No Completado</span>
)}</td>
)}
{columnaCuestionario && (
<td>
{item.cuestionarioCompletado ? (
<span className="badge bg-success">Completado</span>
<td className="text-center align-middle"
style={{ cursor: (idTipoUsuario === 2 && item.status?.idStatus === 4 && !item.cuestionarioCompletado) ? 'pointer' : 'default'}}
onClick={() => (
(idTipoUsuario === 2 && item.status?.idStatus === 4 && !item.cuestionarioCompletado) ? servicioSelectedResponsable(item) : undefined
)}
>
{item.status?.idStatus === 4 ? (
item.cuestionarioCompletado ? (
<span className="badge"></span>
) : (
<span className="badge text-morado bg-light-morado">Cuestionario</span>
)
) : (
<span className="badge bg-danger">No Completado</span>
<span className="badge text-secondary"> </span>
)}
</td>
)}
@@ -268,6 +344,7 @@ const servicioSelected = (row: ServicioSocialResponse | ServicioSocialConCasoEsp
);
}
function formatDate(value?: string | null): string {
if (!value) return "";
@@ -275,7 +352,7 @@ function formatDate(value?: string | null): string {
const d = new Date(value.includes("T") ? value : `${value}T00:00:00`);
if (isNaN(d.getTime())) {
console.warn("⚠️ Fecha inválida:", value);
// console.warn("⚠️ Fecha inválida:", value);
return "";
}
+27
View File
@@ -0,0 +1,27 @@
// Funciones (SIEMPRE primero)
@import "bootstrap/scss/functions";
// VARIABLES PERSONALIZADAS
$primary: #5a2d82;
$secondary: #198754;
$border-radius: 1.5rem;
// Variables base (SIN maps ni dark)
@import "bootstrap/scss/variables";
// Mixins
@import "bootstrap/scss/mixins";
// Core CSS
@import "bootstrap/scss/root";
@import "bootstrap/scss/reboot";
@import "bootstrap/scss/type";
// 6Componentes que se usan
@import "bootstrap/scss/buttons";
@import "bootstrap/scss/forms";
@import "bootstrap/scss/grid";
@import "bootstrap/scss/utilities";
@import "bootstrap/scss/utilities/api";
+36 -14
View File
@@ -1,4 +1,4 @@
@import 'bootstrap/scss/functions';
@import 'bootstrap/dist/css/bootstrap.min.css';
$azul: #003d79;
$dorado: #bd8c01;
@@ -19,8 +19,7 @@ $font-family-sans-serif: 'Roboto', -apple-system, BlinkMacSystemFont, 'Segoe UI'
'Helvetica Neue', Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji',
'Segoe UI Symbol';
@import 'bootstrap/scss/variables';
@import 'bootstrap/scss/variables-dark';
$custom-colors: (
'azul': $azul,
@@ -65,7 +64,7 @@ $custom-colors: (
}
$theme-colors: map-merge($theme-colors, $custom-colors);
$theme-colors: if(variable-exists("theme-colors"), map-merge($theme-colors, $custom-colors), $custom-colors);
.form-registro {
.form-floating {
@@ -93,9 +92,6 @@ $theme-colors: map-merge($theme-colors, $custom-colors);
}
}
@import 'bootstrap/scss/maps';
@import 'bootstrap/scss/mixins';
@import 'bootstrap/scss/bootstrap';
.aviso {
@@ -137,7 +133,7 @@ $theme-colors: map-merge($theme-colors, $custom-colors);
height: 2px;
background-color: $azul;
@include media-breakpoint-up(md) {
@media (min-width: 768px) {
left: 10rem;
}
}
@@ -208,12 +204,12 @@ $theme-colors: map-merge($theme-colors, $custom-colors);
}
.full-center {
//flex-grow-1 d-flex flex-column justify-content-center align-items-center
@extend .flex-grow-1;
@extend .d-flex;
@extend .flex-column;
@extend .justify-content-center;
@extend .align-items-center;
/* Equivalent of Bootstrap utility classes without using @extend */
flex-grow: 1;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.minNavbar {
@@ -268,3 +264,29 @@ $theme-colors: map-merge($theme-colors, $custom-colors);
.imageFlip:hover {
transform: rotateY(360deg);
}
/* Utility classes for custom colors (restore bg-<name> / text-<name> used in JSX)
These are minimal helpers so we don't need to import Bootstrap SCSS utilities. */
.bg-azul { background-color: #003d79 !important; }
.text-azul { color: #003d79 !important; }
.bg-dorado { background-color: #bd8c01 !important; }
.text-dorado { color: #bd8c01 !important; }
.bg-morado { background-color: #714dd2 !important; }
.bg-light-morado { background-color: #d6c9f7 !important; }
.text-morado { color: #714dd2 !important; }
.bg-verde { background-color: #48c78e !important; }
.text-verde { color: #48c78e !important; }
.bg-red-guapa { background-color: #9e1d15 !important; }
.text-red-guapa { color: #9e1d15 !important; }
.bg-blue-guapa { background-color: #3d6cb4 !important; }
.text-blue-guapa { color: #3d6cb4 !important; }
.bg-yellow-guapa { background-color: #eca728 !important; }
.text-yellow-guapa { color: #eca728 !important; }
/* Restore common Bootstrap text utilities if overridden */
.text-white { color: #fff !important; }
.text-dark { color: #000 !important; }
.text-muted { color: #6c757d !important; }
.text-body { color: var(--bs-body-color, #212529) !important; }
.fw-bold { font-weight: 700 !important; }
+8
View File
@@ -0,0 +1,8 @@
import { axiosInstance } from "@/api/config"
import { CasoEspecial } from "@/types/responses"
export const getUserCasoEspecial = async (idCasoEspecial: number): Promise<CasoEspecial[]> => {
const res = await axiosInstance.get(`caso-especial/caso_especial/${idCasoEspecial}`)
return res.data
}
+18 -5
View File
@@ -5,12 +5,12 @@ export interface ServicioSocialResponse {
id?: number;
createdAt?: string;
fecha_creacion?: string | null;
fecha_fin?: string | null;
fecha_inicio?: string | null;
fechaFin?: string | null;
fechaInicio?: string | null;
fecha_registro?: string | null;
Usuario?: Usuario;
Carrera?: Carrera;
Status?: Status;
usuario?: Usuario;
carrera?: Carrera;
status?: Status;
cartaTermino?: boolean;
idCuestionarioPrograma?: number | null;
idCuestionarioPrograma2?: number | null;
@@ -57,4 +57,17 @@ export interface Responsables {
interface ServicioSocialConCasoEspecial extends ServicioSocialResponse {
idCasoEspecial?: number;
}
interface CasoEspecial {
idCasoEspecial: number;
correo?: string;
telefono?: string;
direccion?: string;
fechaInicio?: Date;
fechaFin?: Date;
fechaNacimiento?: Date;
motivo?: string;
dependencia?: string;
institucion?: string;
}
+19 -5
View File
@@ -1,7 +1,11 @@
{
"compilerOptions": {
"target": "ES2017",
"lib": ["dom", "dom.iterable", "esnext"],
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
@@ -11,7 +15,7 @@
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"jsx": "react-jsx",
"incremental": true,
"plugins": [
{
@@ -19,9 +23,19 @@
}
],
"paths": {
"@/*": ["./src/*"]
"@/*": [
"./src/*"
]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts",
".next/dev/types/**/*.ts"
],
"exclude": [
"node_modules"
]
}