1 Commits

Author SHA1 Message Date
frcarlos 88f2ac13d7 fechas 2025-11-04 13:22:07 -06:00
13 changed files with 740 additions and 1105 deletions
-11
View File
@@ -1,11 +0,0 @@
'use client'
import Logout from "@/components/layout/logout";
export default function Layout({ children }: { children: React.ReactNode}) {
return (
<>
<Logout />
{children}
</>
)
}
-42
View File
@@ -1,42 +0,0 @@
"use client";
import BotonRegresar from "@/components/boton-regresar";
import CasoEspecialForm from "@/components/casoEspecial/caso-especial-form";
import { useState } from "react";
export default function Nuevo() {
const [isLoading, setIsLoading] = useState<boolean>(false);
const imprimirError = (error: unknown) => {
alert(`Error: ${JSON.stringify(error)}`);
};
const imprimirMensaje = (message: string) => {
alert(`${message}`);
};
const imprimirWarning = (message: string, onConfirm: () => void) => {
if (confirm(`${message}\n¿Desea continuar?`)) {
onConfirm();
}
};
const updateIsLoading = (value: boolean) => {
setIsLoading(value);
};
return (
<div>
<BotonRegresar />
<h2 style={{ marginLeft: "19rem" }}>Agregar un Servicio Social</h2>
<CasoEspecialForm
imprimirError={imprimirError}
imprimirMensaje={imprimirMensaje}
imprimirWarning={imprimirWarning}
updateIsLoading={updateIsLoading}
/>
{/* <CasoEspecialForm /> */}
</div>
);
}
+45 -77
View File
@@ -4,90 +4,58 @@ import React, { useEffect, useState } from "react";
import { useRouter } from "next/navigation";
import LiberarCasoEspecial from "@/components/casoEspecial/liberacion-caso-especial";
type LocalResponsable = {
idUsuario?: number;
idTipoUsuario?: number;
tipoUsuario?: string | null;
token?: string;
};
type LocalResponsable = { idUsuario?: number; idTipoUsuario?: number; tipoUsuario?: string | null; token?: string };
export default function Page() {
const router = useRouter();
const [responsable, setResponsable] = useState<LocalResponsable>({});
const router = useRouter();
const [responsable, setResponsable] = useState<LocalResponsable>({});
const getMessageFromUnknown = (err: unknown) => {
if (typeof err === "string") return err;
if (err instanceof Error) return err.message;
try {
return JSON.stringify(err);
} catch (_) {
return String(err);
}
};
const getMessageFromUnknown = (err: unknown) => {
if (typeof err === 'string') return err;
if (err instanceof Error) return err.message;
try { return JSON.stringify(err); } catch (_) { return String(err); }
};
const imprimirError = (
err: unknown = {},
title = "¡Hubo un error!",
onConfirm: () => void = () => {}
) => {
const msg = getMessageFromUnknown(err);
// eslint-disable-next-line no-alert
alert(`${title}\n\n${msg}`);
onConfirm();
if (
typeof err === "object" &&
err !== null &&
(err as { err?: unknown }).err === "token error"
) {
try {
localStorage.clear();
} catch (_) {}
router.push("/");
}
};
const imprimirError = (err: unknown = {}, title = '¡Hubo un error!', onConfirm: () => void = () => {}) => {
const msg = getMessageFromUnknown(err);
// eslint-disable-next-line no-alert
alert(`${title}\n\n${msg}`);
onConfirm();
if (typeof err === 'object' && err !== null && (err as { err?: unknown }).err === 'token error') {
try { localStorage.clear(); } catch (_) {}
router.push('/');
}
};
const getLocalhostInfo = () => {
const idUsuario = Number(localStorage.getItem("idUsuario"));
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,
});
};
const getLocalhostInfo = () => {
const idUsuario = Number(localStorage.getItem('idUsuario'));
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 });
};
useEffect(() => {
getLocalhostInfo();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEffect(() => {
getLocalhostInfo();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEffect(() => {
if (responsable.idTipoUsuario === 1) router.push("/admin");
if (responsable.idTipoUsuario === 2) router.push("/responsable");
if (responsable.idTipoUsuario === 3) router.push("/alumno");
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [responsable]);
useEffect(() => {
if (responsable.idTipoUsuario === 1) router.push('/admin');
if (responsable.idTipoUsuario === 2) router.push('/responsable');
if (responsable.idTipoUsuario === 3) router.push('/alumno');
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [responsable]);
return (
<section className="container px-2 pb-6">
<div className="pb-5 pt-6 mt-5 mb-4 border-b border-gray-200">
<button onClick={() => router.push("/casoEspecial/nuevo")}>
Nuevo Caso Especial
</button>
</div>
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>
{responsable.idTipoUsuario !== undefined && responsable.token ? (
<LiberarCasoEspecial
responsable={{
idTipoUsuario: responsable.idTipoUsuario!,
token: responsable.token!,
}}
imprimirError={(msg: string) => imprimirError(msg)}
/>
) : null}
</section>
);
{responsable.idTipoUsuario !== undefined && responsable.token ? (
<LiberarCasoEspecial responsable={{ idTipoUsuario: responsable.idTipoUsuario!, token: responsable.token! }} imprimirError={(msg: string) => imprimirError(msg)} />
) : null}
</section>
);
}
+69 -93
View File
@@ -1,109 +1,85 @@
"use client";
'use client'
import { axiosInstance } from "@/api/config";
import { useRouter } from "next/navigation";
import React from "react";
export default function Home() {
const router = useRouter();
const router = useRouter();
const handleOnSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
const formData = new FormData(e.currentTarget);
const object = Object.fromEntries(formData);
const handleOnSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
const formData = new FormData(e.currentTarget);
const object = Object.fromEntries(formData);
try {
const res = await axiosInstance.post("/usuario/login", object);
try {
const res = await axiosInstance.post('/usuario/login', object);
// Extraer datos del backend
const token = res?.data?.token ?? "";
const usuarioObj = res?.data?.Usuario ?? res?.data ?? {};
const idUsuario = usuarioObj.idUsuario ?? "";
const usuario = usuarioObj.usuario ?? "";
const nombre = usuarioObj.nombre ?? "";
const idTipoUsuario = Number(usuarioObj.TipoUsuario?.idTipoUsuario ?? 0);
//const idTipoUsuario = Number((usuarioObj as any).TipoUsuario?.idTipoUsuario ?? 0);
// Extraer datos del backend
const token = res?.data?.token ?? '';
const usuarioObj = res?.data?.Usuario ?? res?.data ?? {};
const idUsuario = usuarioObj.idUsuario ?? '';
const usuario = usuarioObj.usuario ?? '';
const nombre = usuarioObj.nombre ?? '';
const idTipoUsuario = Number(usuarioObj.TipoUsuario?.idTipoUsuario ?? 0);
//const idTipoUsuario = Number((usuarioObj as any).TipoUsuario?.idTipoUsuario ?? 0);
// Guardar en localStorage
localStorage.setItem("token", String(token));
localStorage.setItem("idUsuario", String(idUsuario));
localStorage.setItem("usuario", String(usuario));
localStorage.setItem("nombre", String(nombre));
localStorage.setItem("idTipoUsuario", String(idTipoUsuario));
// Validar y redirigir según el idTipoUsuario
if (token && idUsuario && idTipoUsuario) {
switch (idTipoUsuario) {
case 1:
router.push("/administrador");
break;
case 2:
router.push("/responsable");
break;
case 3:
router.push("/alumno");
break;
case 4:
router.push("/casoEspecial");
break;
default:
console.warn(`Tipo de usuario desconocido: ${idTipoUsuario}`);
// Guardar en localStorage
localStorage.setItem('token', String(token));
localStorage.setItem('idUsuario', String(idUsuario));
localStorage.setItem('usuario', String(usuario));
localStorage.setItem('nombre', String(nombre));
localStorage.setItem('idTipoUsuario', String(idTipoUsuario));
// Validar y redirigir según el idTipoUsuario
if (token && idUsuario && idTipoUsuario) {
switch (idTipoUsuario) {
case 1:
router.push('/administrador');
break;
case 2:
router.push('/responsable');
break;
case 3:
router.push('/alumno');
break;
case 4:
router.push('/casoEspecial');
break;
default:
console.warn(`Tipo de usuario desconocido: ${idTipoUsuario}`);
localStorage.clear();
break;
}
} else {
localStorage.clear();
console.error('Error: datos de usuario incompletos');
}
} catch (error) {
console.error('Error en el inicio de sesión:', error);
localStorage.clear();
break;
}
} else {
localStorage.clear();
console.error("Error: datos de usuario incompletos");
}
} catch (error) {
console.error("Error en el inicio de sesión:", error);
localStorage.clear();
}
};
};
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}
>
<h2 className="text-center mb-4 fw-bold">IRIS</h2>
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}>
<h2 className="text-center mb-4 fw-bold">IRIS</h2>
<div className="mb-3">
<label htmlFor="usuario" className="form-label">
Usuario
</label>
<input
type="text"
name="usuario"
id="usuario"
className="form-control"
required
/>
<div className="mb-3">
<label htmlFor="usuario" className="form-label">Usuario</label>
<input type="text" name="usuario" id="usuario" className="form-control" 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 />
</div>
<div className="d-grid">
<button type="submit" className="btn btn-primary btn-lg">Iniciar Sesión</button>
</div>
</form>
</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
/>
</div>
<div className="d-grid">
<button type="submit" className="btn btn-primary btn-lg">
Iniciar Sesión
</button>
</div>
</form>
</div>
);
);
}
+1 -1
View File
@@ -76,7 +76,7 @@ export default function Page() {
return (
<section className="container px-2 pb-6">
<div className="pt-6 pb-4">
<div className="pt-6 pb-5">
<p className="is-size-4 block mt-5 h4 mb-4">Estimado(a) responsable del programa de servicio social:</p>
<p className="block">
@@ -70,7 +70,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 un 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 uin archivo este no se selecciona, haga click en cancelar en la ventana emergente e intente de nuevo.</p>
</div>
<input
id="csvInput"
@@ -99,12 +99,6 @@ export default function TablaServicioSocial({ admin, imprimirError }: Props) {
};
<<<<<<< HEAD
export default function TablaServicioSocial() {
=======
>>>>>>> origin/develop
return (
<section>
<div className="columns">
@@ -192,7 +186,6 @@ export default function TablaServicioSocial() {
onPageChange={onPageChange}
idTipoUsuario={admin?.idTipoUsuario}
columnasResponsable={admin?.idTipoUsuario === 1}
columnaFechaRegistro={true}
columnaCuestionario={false}
columnaCartaTermino={false}
columnaCuestionarioCompleto={false}
+15 -16
View File
@@ -1,23 +1,22 @@
"use client";
"use client"
import { useRouter } from "next/navigation";
import React from "react";
import React from "react"
const BotonRegresar: React.FC = () => {
const router = useRouter();
const router = useRouter();
const handleGoBack = () => {
//window.history.back();
router.back();
};
const handleGoBack = () => {
//window.history.back();
router.back();
};
return (
<div style={{ marginLeft: "19rem" }} className="pb-5 pt-3">
<button onClick={handleGoBack} className="btn-outline-primary">
Regresar
</button>
</div>
);
};
return (
<div className="pb-5 pt-3">
<button onClick={handleGoBack} className="btn-outline-primary">Regresar</button>
</div>
)
}
export default BotonRegresar;
export default BotonRegresar
+343 -465
View File
@@ -1,492 +1,373 @@
"use client";
'use client';
import React, { 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 { FaRegCalendarAlt, FaUpload } from "react-icons/fa";
import DatePicker from "react-datepicker";
import BotonRegresar from "../boton-regresar";
import React, { useState, useEffect } from 'react';
import { axiosInstance } from '@/api/config';
import moment from 'moment';
import 'bootstrap/dist/css/bootstrap.min.css';
// 🔹 Tipos estrictos
interface Alumno {
idUsuario?: number;
idCarrera?: number;
nombre?: string;
carrera?: string;
creditos?: string;
idUsuario?: number;
idCarrera?: number;
nombre?: string;
carrera?: string;
creditos?: string;
}
interface Responsable {
//token: { headers: Record<string, string> };
tokenArchivo: { headers: Record<string, string> };
token: { headers: Record<string, string> };
tokenArchivo: { headers: Record<string, string> };
}
interface Props {
//responsable: Responsable;
imprimirError: (msg: { message: string }) => void;
imprimirMensaje: (msg: string) => void;
imprimirWarning: (msg: string, callback: () => void) => void;
updateIsLoading: (value: boolean) => void;
responsable: Responsable;
imprimirError: (msg: { message: string }) => void;
imprimirMensaje: (msg: string) => void;
imprimirWarning: (msg: string, callback: () => void) => void;
updateIsLoading: (value: boolean) => void;
}
export default function CasoEspecialForm({
//responsable,
imprimirError,
imprimirMensaje,
imprimirWarning,
updateIsLoading,
responsable,
imprimirError,
imprimirMensaje,
imprimirWarning,
updateIsLoading,
}: Props) {
// 🔹 Estados del formulario
const [dependencia, setDependencia] = useState("");
const [direccion, setDireccion] = useState("");
const [correo, setCorreo] = useState("");
const [idStatus, setIdStatus] = useState("");
const [institucion, setInstitucion] = useState("");
const [motivo, setMotivo] = useState("");
const [numeroCuenta, setNumeroCuenta] = useState("");
const [telefono, setTelefono] = useState("");
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 [file, setFile] = useState<File | null>(null);
const [minDate, setMinDate] = useState<Date>(new Date("2020-01-02"));
const [minDate2, setMinDate2] = useState<Date>(new Date());
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 || new Date());
};
// Función para resetear campos
const resetear = (): void => {
setDependencia("");
setDireccion("");
setCorreo("");
setIdStatus("");
setInstitucion("");
setMotivo("");
setTelefono("");
setAlumno({});
setFechaInicio(new Date());
setFechaFin(new Date());
setFechaNacimiento(new Date());
setFile(null);
};
// 🔹 Estados del formulario
const [dependencia, setDependencia] = useState('');
const [direccion, setDireccion] = useState('');
const [correo, setCorreo] = useState('');
const [idStatus, setIdStatus] = useState('');
const [institucion, setInstitucion] = useState('');
const [motivo, setMotivo] = useState('');
const [numeroCuenta, setNumeroCuenta] = useState('');
const [telefono, setTelefono] = useState('');
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 [file, setFile] = useState<File | null>(null);
const [minDate, setMinDate] = useState<Date>(new Date('2020-01-02'));
const [minDate2, setMinDate2] = useState<Date>(new Date());
// Validar extensión
const validarExtencion = (archivo: File): void => {
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).",
});
}
};
// Mostrar/ocultar botón de enviar
const mostrarBoton = (): boolean => {
return (
!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}`
);
resetear();
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);
} finally {
updateIsLoading(false);
}
};
// Enviar formulario
const enviar = async (): Promise<void> => {
if (!file) 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,
// Función para resetear campos
const resetear = (): void => {
setDependencia('');
setDireccion('');
setCorreo('');
setIdStatus('');
setInstitucion('');
setMotivo('');
setTelefono('');
setAlumno({});
setFechaInicio(new Date());
setFechaFin(new Date());
setFechaNacimiento(new Date());
setFile(null);
};
const formData = new FormData();
formData.append("alumno", JSON.stringify(data));
formData.append("archivos", file);
try {
updateIsLoading(true);
const res = await axiosInstance.post(`/caso_especial/nuevo`, formData);
resetear();
setNumeroCuenta("");
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 };
}
// Validar extensión
const validarExtencion = (archivo: File): void => {
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).',
});
}
} else if (err instanceof Error) {
msg = { message: err.message };
}
imprimirError(msg);
} finally {
updateIsLoading(false);
}
};
};
// Actualizar fechas automáticamente
const updateFechas = (): void => {
const nuevaFin = new Date(
fechaInicio.getFullYear(),
fechaInicio.getMonth() + 6,
fechaInicio.getDate()
);
setFechaFin(nuevaFin);
setMinDate2(nuevaFin);
};
// Mostrar/ocultar botón de enviar
const mostrarBoton = (): boolean => {
return (
!alumno.idUsuario ||
!telefono ||
!direccion ||
!correo ||
!idStatus ||
(idStatus === '11' && !motivo) ||
(idStatus === '12' && (!dependencia || !institucion)) ||
!file
);
};
// Efectos (equivalentes a watch)
useEffect(() => {
setMotivo("");
setInstitucion("");
setDependencia("");
}, [idStatus]);
// 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);
} 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);
} finally {
updateIsLoading(false);
}
};
useEffect(() => {
if (file && file.size >= 20_000_000) {
imprimirError({ message: "El tamaño del archivo excede los 20MB." });
setFile(null);
} else if (file) {
validarExtencion(file);
}
}, [file]);
// Enviar formulario
const enviar = async (): Promise<void> => {
if (!file) return;
useEffect(() => {
updateFechas();
}, [fechaInicio]);
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,
};
useEffect(() => {
const min = new Date("2020-01-02");
min.setDate(min.getDate() - 1);
setMinDate(min);
updateFechas();
}, []);
const formData = new FormData();
formData.append('alumno', JSON.stringify(data));
formData.append('archivos', file);
{
/* Validaciones del cuestionario */
}
try {
updateIsLoading(true);
const res = await axiosInstance.post(`/caso_especial/nuevo`, formData, responsable.tokenArchivo);
resetear();
setNumeroCuenta('');
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);
} finally {
updateIsLoading(false);
}
};
// 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
// Actualizar fechas automáticamente
const updateFechas = (): void => {
const nuevaFin = new Date(
fechaInicio.getFullYear(),
fechaInicio.getMonth() + 6,
fechaInicio.getDate()
);
setFechaFin(nuevaFin);
setMinDate2(nuevaFin);
};
// Efectos (equivalentes a watch)
useEffect(() => {
setMotivo('');
setInstitucion('');
setDependencia('');
}, [idStatus]);
useEffect(() => {
if (file && file.size >= 20_000_000) {
imprimirError({ message: 'El tamaño del archivo excede los 20MB.' });
setFile(null);
} else if (file) {
validarExtencion(file);
}
}, [file]);
useEffect(() => {
updateFechas();
}, [fechaInicio]);
useEffect(() => {
const min = new Date('2020-01-02');
min.setDate(min.getDate() - 1);
setMinDate(min);
updateFechas();
}, []);
{/* 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
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>
</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"
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" style={{ padding: "1rem 1rem" }}>
{alumno.nombre || ""}
</p>
</div>
<div className="mb-3">
<label className="form-label">Carrera</label>
<p className="form-control" style={{ padding: "1rem 1rem" }}>
{alumno.carrera || ""}
</p>
</div>
<div className="mb-3">
<label className="form-label">Créditos</label>
<p className="form-control" style={{ padding: "1rem 1rem" }}>
{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>
<Col>
<FormGroup>
<FormLabel>Fecha de inicio</FormLabel>
<InputGroup>
<InputGroup.Text>
<FaRegCalendarAlt />
</InputGroup.Text>
<DatePicker
selected={fechaInicio}
onChange={handleFechaInicioChange}
minDate={minDate}
dateFormat="yyyy-MM-dd"
className="form-control"
wrapperClassName="flex-grow-1"
calendarClassName="mi-calendario"
/>
</InputGroup>
</FormGroup>
</Col>
<Col>
<FormGroup>
<FormLabel>Fecha de fin</FormLabel>
<InputGroup>
<InputGroup.Text>
<FaRegCalendarAlt />
</InputGroup.Text>
<DatePicker
selected={fechaFin}
onChange={handleFechaFinChange}
value={fechaFin.toISOString().substring(0, 10)}
minDate={minDate2}
dateFormat="yyyy-MM-dd"
className="form-control"
wrapperClassName="flex-grow-1"
calendarClassName="mi-calendario"
/>
</InputGroup>
</FormGroup>
</Col>
<Col>
<FormGroup>
<FormLabel>Fecha de nacimiento</FormLabel>
<InputGroup>
<InputGroup.Text>
<FaRegCalendarAlt />
</InputGroup.Text>
<DatePicker
selected={fechaNacimiento}
maxDate={new Date()}
onChange={handleFechaNacimientoChange}
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">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>
<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)}
/>
</div>
{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>
)}
<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>
</>
)}
{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>
<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">Tmañ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"
style={{ display: "none" }}
onChange={(e) => setFile(e.target.files ? e.target.files[0] : null)}
/>
</FormGroup>
<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>
</>
)}
{/*
<div className="mb-3">
<div className="mb-3">
<label className="form-label">Archivo (.zip, .rar)</label>
<input
type="file"
@@ -496,24 +377,21 @@ export default function CasoEspecialForm({
/>
<div className="form-text">Tamaño máximo 20MB</div>
</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
)
}
>
Enviar
</button>
</div>
<BotonRegresar />
</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>
);
}
@@ -3,8 +3,6 @@
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 { FaInfoCircle, FaSchool, FaUser } from "react-icons/fa";
interface Responsable {
idTipoUsuario: number;
@@ -122,82 +120,8 @@ export default function LiberarCasoEspecial({ responsable, imprimirError }: Prop
<div className="mb-6 container mt-4">
<h3 className="h4 mb-4">Casos Especiales</h3>
<section className="container-fluid my-3">
<Form onSubmit={(e) => { e.preventDefault(); obtenerCasosEspeciales(); }}>
<Row className="g-3">
<Col md={3}>
<FormGroup>
<FormLabel>Número de Cuenta</FormLabel>
<InputGroup>
<InputGroup.Text className="rounded-4">
<FaSchool />
</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>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>Status</FormLabel>
<InputGroup>
<InputGroup.Text className="rounded-4">
<FaInfoCircle />
</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
@@ -211,7 +135,7 @@ export default function LiberarCasoEspecial({ responsable, imprimirError }: Prop
/>
</div>
{/* Nombre */}
<div className="col-md-3">
<label className="form-label">Nombre</label>
<input
@@ -224,6 +148,7 @@ export default function LiberarCasoEspecial({ responsable, imprimirError }: Prop
/>
</div>
{/* Status */}
<div className="col-md-3">
<label className="form-label">Status</label>
<select
@@ -242,7 +167,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"
@@ -253,17 +178,6 @@ 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 */}
{/*
+24 -22
View File
@@ -2,28 +2,30 @@ import { useRouter } from "next/navigation";
import { useEffect, useState } from "react";
export default function Logout() {
const router = useRouter();
const [usuario, setUsuario] = useState<string | null>(null);
const router = useRouter();
const [usuario, setUsuario] = useState<string | null>(null);
useEffect(() => {
const storedUsuario = localStorage.getItem("usuario");
setUsuario(storedUsuario);
}, []);
useEffect(() => {
const storedUsuario = localStorage.getItem("usuario")
setUsuario(storedUsuario);
}, []);
const handleLogout = () => {
localStorage.removeItem("token");
localStorage.removeItem("usuario");
router.push("/");
};
const handleLogout = () => {
localStorage.removeItem("token");
localStorage.removeItem('usuario');
router.push("/");
}
return(
<section className="bg-dorado">
<div className="container d-flex justify-content-between align-items-center">
<h2 className="m-2 text-white">{ usuario }</h2>
<button onClick={handleLogout} className="m-2 bg-danger">
Cerrar Sesion
</button>
</div>
</section>
)
return (
<section className="bg-dorado">
<div className="container d-flex justify-content-between align-items-center">
<h2 className="m-2 text-white">{usuario}</h2>
<button onClick={handleLogout} className="m-2 bg-danger">
Cerrar Sesion
</button>
</div>
</section>
);
}
}
+236 -278
View File
@@ -2,313 +2,271 @@
import { useEffect, useState } from "react";
import { axiosInstance } from "@/api/config";
import { ServicioSocialConCasoEspecial, ServicioSocialResponse } from "@/types/responses";
import {
ServicioSocialConCasoEspecial,
ServicioSocialResponse,
} from "@/types/responses";
import { useRouter } from "next/navigation";
interface Props {
data?: ServicioSocialResponse[];
total?: number;
onPageChange?: (newPage: number) => void;
columnaFechaInicio?: boolean;
columnaFechaFin?: boolean;
idTipoUsuario?: number; // 1=admin, 2=responsable, 3=alumno, 4=caso especial
columnasResponsable?: boolean;
onRowAction?: (row: ServicioSocialResponse, path: string) => void;
columnaCuestionario?: boolean;
columnaCartaTermino?: boolean;
columnaCuestionarioCompleto?: boolean;
columnaFechaRegistro?: boolean;
data?: ServicioSocialResponse[];
total?: number;
onPageChange?: (newPage: number) => void;
columnaFechaInicio?: boolean;
columnaFechaFin?: boolean;
idTipoUsuario?: number; // 1=admin, 2=responsable, 3=alumno, 4=caso especial
columnasResponsable?: boolean;
onRowAction?: (row: ServicioSocialResponse, path: string) => void;
columnaCuestionario?: boolean;
columnaCartaTermino?: boolean;
columnaCuestionarioCompleto?: boolean;
columnaFechaRegistro?: boolean;
}
export default function ServicioSocialTabla({
data,
total = 0,
onPageChange,
idTipoUsuario,
columnasResponsable = false,
columnaFechaInicio = false,
columnaFechaFin = false,
columnaCuestionario = false,
columnaCartaTermino = false,
columnaCuestionarioCompleto = false,
columnaFechaRegistro = false,
onRowAction,
data,
total = 0,
onPageChange,
idTipoUsuario,
columnasResponsable = false,
columnaFechaInicio = true,
columnaFechaFin = true,
columnaCuestionario = false,
columnaCartaTermino = false,
columnaCuestionarioCompleto = false,
columnaFechaRegistro = false,
onRowAction,
}: Props) {
const [info, setInfo] = useState<ServicioSocialResponse[]>(data || []);
const [loading, setLoading] = useState(false);
const [currentPage, setCurrentPage] = useState(1);
const [info, setInfo] = useState<ServicioSocialResponse[]>(data || []);
const [loading, setLoading] = useState(false);
const [currentPage, setCurrentPage] = useState(1);
const router = useRouter();
const router = useRouter();
//const columnaFechaInicio = true;
//const columnaFechaFin = true;
// 🔹 Cargar datos del padre si existen
useEffect(() => {
if (data && data.length > 0) {
setInfo(data);
}
}, [data]);
// 🔹 Paginación simple
function handlePrev() {
if (currentPage > 1) {
const np = currentPage - 1;
setCurrentPage(np);
onPageChange?.(np);
}
// 🔹 Cargar datos del padre si existen
useEffect(() => {
if (data && data.length > 0) {
setInfo(data);
}
}, [data]);
function handleNext() {
const lastPage = Math.max(1, Math.ceil(total / 10));
if (currentPage < lastPage) {
const np = currentPage + 1;
setCurrentPage(np);
onPageChange?.(np);
}
// 🔹 Paginación simple
function handlePrev() {
if (currentPage > 1) {
const np = currentPage - 1;
setCurrentPage(np);
onPageChange?.(np);
}
const servicioSelected = (row: ServicioSocialResponse | ServicioSocialConCasoEspecial) => {
if (!row) return;
if (idTipoUsuario === 1) {
if (row.idServicio) {
localStorage.setItem("idServicio", String(row.idServicio));
}
if ("idCasoEspecial" in row && row.idCasoEspecial) {
localStorage.setItem("idCasoEspecial", String(row.idCasoEspecial));
}
router.push("/administrador/servicio");
} else if (
row.idServicio ||
("idCasoEspecial" in row && row.idCasoEspecial)
) {
console.log("Limpiando selección (no admin)");
}
}
/*
const servicioSelected = (row: ServicioSocialResponse | ServicioSocialConCasoEspecial) => {
if (!row) return;
if (idTipoUsuario === 1) {
if (row.idServicio) {
localStorage.setItem("idServicio", String(row.idServicio));
}
// ✅ forma segura sin any
if ("idCasoEspecial" in row && row.idCasoEspecial) {
localStorage.setItem("idCasoEspecial", String(row.idCasoEspecial));
}
router.push("/administrador/servicio");
} else if (
row.idServicio ||
("idCasoEspecial" in row && row.idCasoEspecial)
) {
console.log("Limpiando selección (no admin)");
}
};
// Codigo original
const servicioSelected = (row: ServicioSocialResponse | ServicioSocialConCasoEspecial) => {
if (!row) return;
function handleNext() {
const lastPage = Math.max(1, Math.ceil(total / 10));
if (currentPage < lastPage) {
const np = currentPage + 1;
setCurrentPage(np);
onPageChange?.(np);
}
}
if (idTipoUsuario === 1) {
const servicioSelected = (
row: ServicioSocialResponse | ServicioSocialConCasoEspecial
) => {
if (!row) return;
if (row.idServicio) {
localStorage.setItem("idServicio", String(row.idServicio));
}
if ((row as any).idCasoEspecial) {
localStorage.setItem("idCasoEspecial", String((row as any).idCasoEspecial));
}
if (idTipoUsuario === 1) {
if (row.idServicio) {
localStorage.setItem("idServicio", String(row.idServicio));
}
// Redirigir a la vista de detalle
router.push("/administrador/servicio");
} else if (row.idServicio || (row as any).idCasoEspecial) {
console.log("Limpiando selección (no admin)");
if ("idCasoEspecial" in row && row.idCasoEspecial) {
localStorage.setItem("idCasoEspecial", String(row.idCasoEspecial));
}
}
};
router.push("/administrador/servicio");
} else if (
row.idServicio ||
("idCasoEspecial" in row && row.idCasoEspecial)
) {
console.log("Limpiando selección (no admin)");
}
};
*/
return (
<section>
{loading ? (
<div>Cargando...</div>
) : (
<>
<div className="table-responsive">
<table className="table table-striped">
<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>}
</tr>
</thead>
<tbody>
{info.map((item, index) => (
<tr
key={index}
style={{
cursor: idTipoUsuario === 1 ? "pointer" : "default",
}}
onClick={() => (
idTipoUsuario === 1 ? servicioSelected(item) : undefined
)}
//onClick={() => Falta corregir
//idTipoUsuario === 1 ? servicioSelected(item) : undefined
//}
>
{columnaFechaRegistro && <td>{formatDate(item.createdAt)}</td>}
<td>{item.Usuario?.usuario}</td>
<td>{item.Usuario?.nombre}</td>
<td>{item.Carrera?.carrera}</td>
{columnaFechaInicio && (
<td>{formatDate(item.fechaInicio)}</td>
)}
{columnaFechaFin && <td>{formatDate(item.fechaFin)}</td>}
<td>
{columnasResponsable && item.Status?.idStatus === 7 ? (
<button
className="btn btn-sm btn-danger"
onClick={() =>
onRowAction?.(item, "carta_aceptacion")
}
>
Carta Aceptación Rechazada
</button>
) : (
<span
className={`badge bg-${statusClass(
item.Status?.idStatus
)}`}
>
{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>
)}
{columnaCuestionarioCompleto && (
<td>{item.cuestionarioCompletado ? (
<span className="badge bg-success">Completado</span>
) : (
<span className="badge bg-danger">No Completado</span>
)}</td>
)}
{columnaCuestionario && (
<td>
{item.cuestionarioCompletado ? (
<span className="badge bg-success">Completado</span>
) : (
<span className="badge bg-danger">No Completado</span>
)}
</td>
)}
</tr>
))}
{info.length === 0 && (
<tr>
<td colSpan={7} className="text-center">
No hay registros
</td>
</tr>
)}
</tbody>
</table>
</div>
<div className="d-flex justify-content-between align-items-center mt-2">
<div>Total: {total}</div>
<div>
<button
className="btn btn-sm btn-outline-primary me-2"
onClick={handlePrev}
disabled={currentPage <= 1}
>
Anterior
</button>
<span>Página {currentPage}</span>
<button
className="btn btn-sm btn-outline-primary ms-2"
onClick={handleNext}
disabled={
currentPage >= Math.max(1, Math.ceil(total / 10))
return (
<section>
{loading ? (
<div>Cargando...</div>
) : (
<>
<div className="table-responsive">
<table className="table table-striped">
<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>}
</tr>
</thead>
<tbody>
{info.map((item, index) => (
<tr
key={index}
style={{
cursor: idTipoUsuario === 1 ? "pointer" : "default",
}}
onClick={() =>
idTipoUsuario === 1 ? servicioSelected(item) : undefined
}
>
Siguiente
</button>
</div>
>
{/* Si el padre pidió mostrar Fecha Registro, la mostramos aquí (createdAt) */}
{columnaFechaRegistro && (
<td>{formatDate((item as any).createdAt)}</td>
)}
<td>{item.Usuario?.usuario}</td>
<td>{item.Usuario?.nombre}</td>
<td>{item.Carrera?.carrera}</td>
{/* Usar las propiedades en camelCase que vienen en tu JSON */}
{columnaFechaInicio && (
<td>{formatDate((item as any).fechaInicio)}</td>
)}
{columnaFechaFin && (
<td>{formatDate((item as any).fechaFin)}</td>
)}
<td>
{columnasResponsable && item.Status?.idStatus === 7 ? (
<button
className="btn btn-sm btn-danger"
onClick={() =>
onRowAction?.(item, "carta_aceptacion")
}
>
Carta Aceptación Rechazada
</button>
) : (
<span
className={`badge bg-${statusClass(
item.Status?.idStatus
)}`}
>
{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>
)}
{columnaCuestionarioCompleto && (
<td>
{item.cuestionarioCompletado ? (
<span className="badge bg-success">Completado</span>
) : (
<span className="badge bg-danger">No Completado</span>
)}
</td>
)}
{columnaCuestionario && (
<td>
{item.cuestionarioCompletado ? (
<span className="badge bg-success">Completado</span>
) : (
<span className="badge bg-danger">No Completado</span>
)}
</td>
)}
</tr>
))}
{info.length === 0 && (
<tr>
<td colSpan={7} className="text-center">
No hay registros
</td>
</tr>
)}
</tbody>
</table>
</div>
<div className="d-flex justify-content-between align-items-center mt-2">
<div>Total: {total}</div>
<div>
<button
className="btn btn-sm btn-outline-primary me-2"
onClick={handlePrev}
disabled={currentPage <= 1}
>
Anterior
</button>
<span>Página {currentPage}</span>
<button
className="btn btn-sm btn-outline-primary ms-2"
onClick={handleNext}
disabled={currentPage >= Math.max(1, Math.ceil(total / 10))}
>
Siguiente
</button>
</div>
</>
)}
</section>
);
</div>
</>
)}
</section>
);
}
function formatDate(value?: string | null): string {
if (!value) return "";
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`);
const d = new Date(value.includes("T") ? value : `${value}T00:00:00`);
if (isNaN(d.getTime())) {
console.warn("⚠️ Fecha inválida:", value);
return "";
}
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();
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}`;
return `${day}/${month}/${year}`;
}
function statusClass(id?: number) {
switch (id) {
case 1:
return "dark";
case 2:
return "info";
case 3:
return "warning";
case 4:
return "primary";
case 5:
case 6:
return "success";
case 7:
case 8:
case 9:
case 10:
return "danger";
default:
return "secondary";
}
switch (id) {
case 1:
return "dark";
case 2:
return "info";
case 3:
return "warning";
case 4:
return "primary";
case 5:
case 6:
return "success";
case 7:
case 8:
case 9:
case 10:
return "danger";
default:
return "secondary";
}
}
+2 -2
View File
@@ -5,8 +5,8 @@ export interface ServicioSocialResponse {
id?: number;
createdAt?: string;
fecha_creacion?: string | null;
fechaFin?: string | null;
fechaInicio?: string | null;
fecha_fin?: string | null;
fecha_inicio?: string | null;
fecha_registro?: string | null;
Usuario?: Usuario;
Carrera?: Carrera;