15 Commits

Author SHA1 Message Date
stern 644076584c Add deploy.yaml
Despliegue Automatizado Universal / get-context (push) Successful in 1s
Despliegue Automatizado Universal / build (push) Successful in 1m19s
Despliegue Automatizado Universal / deploy (push) Successful in 43s
2026-07-02 03:39:28 -06:00
Emilio-11 12d4031281 cambio a la hora de mostrar el objeto 2026-02-10 12:43:18 -06:00
IO 7ee5c28208 se agregò check box 2026-02-04 13:04:46 -06:00
evenegas 393c25da87 Se actualizo la version de next 2026-01-09 09:17:47 -06:00
evenegas ff33729aba Error 2025-09-12 12:35:33 -06:00
evenegas ae4029ac4e Se agregaron usuarios externos 2025-09-09 11:03:41 -06:00
evenegas 7475ca2fbd Merge branch 'develop' of https://repositorio.acatlan.unam.mx/CIDWA/cargaMasiva_front into develop 2025-09-03 12:27:48 -06:00
evenegas adb11c1965 agregar botón google 2025-09-03 12:22:42 -06:00
evenegas a9fb5d5a88 Se agregaró inicio de Google y contraseña 2025-09-02 14:00:07 -06:00
stern cb9d15a103 Uso de NodeJS 22 en script start 2025-08-18 11:01:02 -06:00
evenegas 76ff60304a Se agregaron condiciones de texto 2025-08-18 10:43:47 -06:00
evenegas 09d9e56c93 Se agregaron notas 2025-08-15 10:28:18 -06:00
evenegas f155313b82 Se agregaron notas 2025-08-15 10:18:15 -06:00
evenegas 5f8c06dd6a se agregaron textos y cambio de orden de componentes 2025-08-14 10:16:23 -06:00
evenegas 5d5210e48a cambio de estilo 2025-07-31 13:58:00 -06:00
15 changed files with 1573 additions and 495 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"
+879 -410
View File
File diff suppressed because it is too large Load Diff
+5 -2
View File
@@ -5,7 +5,7 @@
"scripts": {
"dev": "next dev --turbopack",
"build": "next build",
"start": "next start",
"start": "source ~/.nvm/nvm.sh && nvm use 22.10.0 && npm install && next build && next start -p 3256",
"lint": "next lint"
},
"dependencies": {
@@ -14,7 +14,7 @@
"bootstrap-icons": "^1.13.1",
"framer-motion": "^12.18.1",
"jwt-decode": "^4.0.0",
"next": "15.3.3",
"next": "^16.0.7",
"react": "^19.0.0",
"react-dom": "^19.0.0"
},
@@ -23,7 +23,10 @@
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
"autoprefixer": "^10.4.21",
"postcss": "^8.5.6",
"sass": "^1.32.13",
"tailwindcss": "^4.1.12",
"typescript": "^5"
}
}
+79 -5
View File
@@ -14,6 +14,64 @@ const tiposUsuarioEspecial = ["Caso especial"];
const tiposUsuarioTrabajador = ["Profesor", "Trabajadores"];
const opcionesCarrera = [
"ACTUARIA",
"ARQUITECTURA",
"CIENCIA DE DATOS",
"CIENCIAS POLITICAS Y ADMINISTRACIÓN PUBLICA",
"COMUNICACION",
"COMUNIDAD EXTERNA",
"DERECHO",
"DERECHO (SUA)",
"DISEÑO GRAFICO",
"DOCTORADO EN CIENCIAS POLÍTICAS Y SOCIALES",
"DOCTORADO EN DERECHO",
"DOCTORADO EN ECONOMÍA",
"DOCTORADO EN PEDAGOGÍA",
"DOCTORADO EN URBANISMO",
"ECONOMIA",
"ENSEÑANZA DE ALEMÁN (LENG.EXTRANJ)SUAyED",
"ENSEÑANZA DE ESPAÑOL(LENG.EXTRANJ)SUAyED",
"ENSEÑANZA DE FRANCÉS(LENG.EXTRANJ)SUAyED",
"ENSEÑANZA DE INGLES",
"ENSEÑANZA DE INGLÉS (LENG.EXTRANJ)SUAyED",
"ENSEÑANZA DE ITALIANO(LENG EXTRANJ) SUAyED",
"ESPECIALIZACIÓN EN COSTOS EN LA CONSTRUCCION",
"ESPECIALIZACIÓN EN DERECHO ADMINISTRATIVO",
"ESPECIALIZACIÓN EN DERECHO DE LA PROPIEDAD INTELECTUAL",
"ESPECIALIZACIÓN EN DERECHO FISCAL",
"ESPECIALIZACIÓN EN DERECHO NOTARIAL Y REGISTRAL",
"ESPECIALIZACIÓN EN DERECHO PENAL",
"ESPECIALIZACIÓN EN DERECHO SANITARIO",
"ESPECIALIZACIÓN EN DERECHOS HUMANOS",
"ESPECIALIZACIÓN EN GENERO Y DERECHO",
"ESPECIALIZACIÓN EN GEOTECNIA",
"ESPECIALIZACIÓN EN INSTITUCIONES ADMINISTRATIVAS DE FINANZAS PÚBLICAS",
"ESPECIALIZACIÓN EN SISTEMAS DE CALIDAD",
"ESPECIALIZACIÓN EN TECNOLOGIA DIGITAL PARA LA ENSEÑANZA DE MATEMÁTICAS",
"FILOSOFIA",
"HISTORIA",
"INGENIERIA CIVIL",
"LENGUA Y LITERATURA HISPANICAS",
"MAESTRÍA EN COMUNICACIÓN",
"MAESTRÍA EN DERECHO",
"MAESTRÍA EN DOCENCIA PARA LA EDUCACIÓN MEDIA SUPERIOR",
"MAESTRÍA EN ECONOMÍA",
"MAESTRÍA EN ESTUDIOS EN RELACIONES INTERNACIONALES",
"MAESTRÍA EN ESTUDIOS MÉXICO - ESTADOS UNIDOS",
"MAESTRÍA EN ESTUDIOS POLÍTICOS Y SOCIALES",
"MAESTRÍA EN GOBIERNO Y ASUNTOS PÚBLICOS",
"MAESTRÍA EN PEDAGOGÍA",
"MAESTRÍA EN POLÍTICA CRIMINAL",
"MAESTRÍA EN URBANISMO",
"MATEMÁTICAS APLICADAS Y COMPUTACIÓN",
"PEDAGOGIA",
"PERIODISMO Y COMUNICACION COLECTIVA",
"RELAC. INTERNACIONALES (SUA)",
"RELACIONES INTERNACIONALES",
"SOCIOLOGIA",
];
const FormularioCargaIndividual = () => {
const [tipoUsuario, setTipoUsuario] = useState("");
const [formData, setFormData] = useState<any>({});
@@ -22,7 +80,7 @@ const FormularioCargaIndividual = () => {
const [guardando, setGuardando] = useState(false); // 🔹 Estado para controlar el botón
const camposEstudiante = [
"num_cuenta", "carrera", "clave_carrera", "nombre",
"num_cuenta", "carrera", "nombre",
"a_paterno", "a_materno", "fecha_nacimiento", "genero", "generacion",
];
@@ -33,7 +91,7 @@ const FormularioCargaIndividual = () => {
const camposTrabajador = [
"num_cuenta", "nombre", "a_paterno", "a_materno", "rfc",
"fecha_nacimiento", "genero", "carrera", "clave_carrera",
"fecha_nacimiento", "genero",
];
const camposVisibles =
@@ -114,7 +172,7 @@ const FormularioCargaIndividual = () => {
if (!response.ok) {
const error = await response.json();
console.error("Error del servidor:", error);
setMensaje("Ocurrió un error al guardar el usuario ❌");
setMensaje(error.message);
return;
}
@@ -123,7 +181,7 @@ const FormularioCargaIndividual = () => {
setTipoUsuario("");
} catch (err) {
console.error("Error en petición fetch:", err);
setMensaje("Ocurrió un error al guardar el usuario ❌");
setMensaje(err instanceof Error ? err.message : String(err));
} finally {
setGuardando(false); // 🔹 Termina de guardar
}
@@ -172,7 +230,23 @@ const FormularioCargaIndividual = () => {
<option value="M">M</option>
<option value="F">F</option>
</select>
) : (
) : campo === "carrera" ? (
// 🔹 Select de carrera
<select
name="carrera"
className={`form-select ${errores.includes(campo) ? "is-invalid" : ""}`}
value={formData.carrera || ""}
onChange={handleChange}
required
>
<option value="">Selecciona una carrera</option>
{opcionesCarrera.map((c) => (
<option key={c} value={c}>
{c}
</option>
))}
</select>
):(
<input
type={campo === "fecha_nacimiento" ? "date" : "text"}
name={campo}
+74 -10
View File
@@ -6,6 +6,7 @@ import CargaArchivo from "@/components/cargaArchivo";
import Button from "@/components/button";
import { useAuthRedirect } from "@/hooks/auth";
import { getUserFromToken } from "@/components/Hook";
import { useSession } from "@/hooks/use-session";
const apiUrl = process.env.NEXT_PUBLIC_API_URL;
@@ -21,6 +22,8 @@ export default function Dashboard() {
const loading = useAuthRedirect();
const [fuentes, setFuentes] = useState<Fuente[]>([]);
const [descargando, setDescargando] = useState(false);
const { data: user, } = useSession()
const fetchMovimientos = async () => {
const token = localStorage.getItem("token");
@@ -56,6 +59,9 @@ export default function Dashboard() {
fetchMovimientos();
}, []);
const handleDownload = async () => {
const token = localStorage.getItem("token");
if (!token) {
@@ -111,8 +117,70 @@ export default function Dashboard() {
return (
<main>
{user?.tipo !== "LOAD" ? <div className="descarga-info p-4">
<p className="descargar-text ">
<span className="fw-bold">Descargar:</span> obtiene todos los usuarios que se han
ingresado de las distintas fuentes o cargas.
</p>
<p className="mt-4 fw-bold importante-text">Importante</p>
<ol className="puntos-text">
<li>
Seleccionar el botón de validar de todas las fuentes que estaban a la
hora de que descargaste la base de datos, eso ayuda a no volver a
descargar los mismos datos y permite informar al usuario el status de
los servicios que tiene activos.
</li>
<li>
Si das validar antes de haber descargado los datos{" "}
<span className="fw-bold">YA NO PODRÁS DESCARGAR</span> los datos de
esa fuente.
</li>
</ol>
</div> : null}
<div className="flex gap-4 flex-wrap mb-6">
<h2 className="text-xl font-semibold mb-4">Fuentes</h2>
<h2 className="text-xl font-semibold mt-5 mb-5">Descarga de Datos</h2>
{user?.tipo !== "LOAD" ? <h5 className="!text-[32px] text-gray-700 leading-tight mb-3">
Paso 1. Descarga el archivo.
</h5> : null}
<Button
onClick={handleDownload}
disabled={descargando}
className="mb-4, mt-3"
variant="azul"
>
{descargando ? "Descargando..." : "Descarga Base de Datos"}
</Button>
<h2 className="text-xl font-semibold mb-4, mt-4">Fuentes</h2>
{user?.tipo !== "LOAD" ? <><h5 className="text-xs !text-gray-700 leading-tight mb-4">
Paso 2. Alimenta la BD del servicio asigando a este perfil.
</h5><h5 className="text-xs !text-gray-700 leading-tight mb-4">
Paso 3. Selecciona este botón al haber concluido el paso anterior.
</h5></> : null}
<div
style={{
display: "flex",
@@ -148,15 +216,11 @@ export default function Dashboard() {
</>
)}
<h2 className="text-xl font-semibold mt-5 mb-4">Descarga de Datos</h2>
<Button
onClick={handleDownload}
disabled={descargando}
className="mb-3"
variant="azul"
>
{descargando ? "Descargando..." : "Descarga Base de Datos"}
</Button>
</div>
</main>
);
+59 -2
View File
@@ -7,13 +7,49 @@
body {
margin: 0;
font-family: "Segoe UI", sans-serif;
/* background: radial-gradient(circle at center, #f8f4ff, hsl(218, 45%, 37%)); */
background: radial-gradient(circle at center, #f8f4ff, #d5a00f65);
background: radial-gradient(circle at center, #f8f4ff, hsl(218, 45%, 37%));
/* background: radial-gradient(circle at center, #f8f4ff, #d5a00f65); */
color: #1a1a1a;
text-align: center;
}
/* Contenedor centrado y ancho limitado */
/* Contenedor centrado y ancho limitado */
.descarga-info {
background-color: #ffffff;
border-radius: 0.5rem;
box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
max-width: 700px;
margin: 2rem auto;
padding: 1.5rem;
}
/* Descargar texto inline con espacio */
.descargar-text {
font-size: 18px;
color: #000000;
}
/* Importante */
.importante-text {
font-size: 16px;
color: #000000;
}
/* Lista numerada justificada y con separación */
.puntos-text {
text-align: justify;
margin-left: 1.5rem;
margin-top: 1rem;
}
.puntos-text li {
margin-bottom: 0.5rem;
line-height: 1.5;
}
@@ -151,4 +187,25 @@ body>p:last-of-type {
.invert {
filter: invert(1);
}
.boton {
border: #cfcece;
border-radius: 8px;
padding: 0.6em 1.2em;
font-size: 1em;
font-weight: 100;
font-family: inherit;
background-color: #d4d2d2;
cursor: pointer;
transition: border-color 0.5s;
padding: 1em;
}
.imagen {
background-color: #cfcbcb;
max-height: 25px;
max-width: 25px;
margin-right: 35px;
}
+29
View File
@@ -0,0 +1,29 @@
"use client";
import { useEffect } from "react";
import { useRouter } from "next/navigation";
export default function OAuthCallback() {
const router = useRouter();
useEffect(() => {
const params = new URLSearchParams(window.location.search);
const token = params.get("token");
if (token) {
// Guardar token en localStorage
localStorage.setItem("token", token);
// Disparar evento para que useSession se actualice
window.dispatchEvent(new Event("tokenChanged"));
// Redirigir al Home
router.replace("/");
} else {
// No hay token: redirigir a login
router.replace("/");
}
}, [router]);
return <p>Procesando inicio de sesión...</p>;
}
+89 -43
View File
@@ -10,10 +10,14 @@ import LoginPage from "@/containers/LoginPage";
import FormularioCargaIndividual from "./alta/alta";
import { useSession } from "@/hooks/use-session";
import { useEffect, useState } from "react";
import path from "path";
import { motion } from "framer-motion";
export type PageKey = "landing" | "login" | "carga" | "consulta" | "carga individual";
export type PageKey =
| "landing"
| "login"
| "carga"
| "consulta"
| "carga individual";
export interface HeaderItem {
label: string;
@@ -23,62 +27,100 @@ export interface HeaderItem {
const Home = () => {
const [page, setPage] = useState<PageKey>("landing");
const { data: user, loading } = useSession(); // usuario viene de useSession()
const [tokenProcessed, setTokenProcessed] = useState(false); // ✅ Esperar token
const { data: user, loading } = useSession();
const [isCarga, setIsCarga] = useState("Carga");
const [saludo, setSaludo] = useState("");
// 🔹 Capturar token antes de montar la UI
useEffect(() => {
const params = new URLSearchParams(window.location.search);
const token = params.get("token");
const error = params.get("error");
if (token) {
localStorage.setItem("token", token);
window.dispatchEvent(new Event("tokenChanged"));
// Limpiar token de URL
const cleanUrl = window.location.origin + window.location.pathname;
window.history.replaceState({}, document.title, cleanUrl);
}
if (error) {
// Aquí decides cómo manejarlo
alert("Error de inicio de sesión: NO AUTORIZADO");
// Si quieres limpiar la URL después
const cleanUrl = window.location.origin + window.location.pathname;
window.history.replaceState({}, document.title, cleanUrl);
}
setTokenProcessed(true); // ✅ Ya procesamos token
}, []);
// 🔹 Saludo
useEffect(() => {
const origen = user?.tipo;
if (origen === "LOAD") {
setSaludo("Bienvenido al módulo de carga de datos");
} else if (origen === "RED") {
setSaludo("Bienvenido usuario de red");
} else if (origen === "AT") {
setSaludo("Bienvenido usuario de AT");
} else if (origen === "CORREO") {
setSaludo("Bienvenido usuario de correo");
} else if (origen === "SOLICITA") {
setSaludo("Bienvenido usuario de préstamos PC Puma");
}
if (origen === "LOAD") setSaludo("Bienvenido al módulo de carga de datos");
else if (origen === "RED") setSaludo("Bienvenido usuario de red");
else if (origen === "AT") setSaludo("Bienvenido usuario de AT");
else if (origen === "CORREO") setSaludo("Bienvenido usuario de correo");
else if (origen === "SOLICITA")setSaludo("Bienvenido usuario de préstamos PC Puma");
else if (origen === "EXTERNO") setSaludo("Bienvenido usuario externo");
}, [user]);
// 🔹 Label carga/descarga
useEffect(() => {
if (user?.tipo === "LOAD") {
setIsCarga("Carga");
} else {
setIsCarga("Descarga");
}
if (user?.tipo === "LOAD") setIsCarga("Carga");
else setIsCarga("Descarga");
}, [user]);
// 🔹 Redirigir automáticamente si ya hay sesión
useEffect(() => {
if (!loading && user && page === "login") setPage("landing");
}, [loading, user, page]);
const handleLogout = () => {
localStorage.removeItem("token");
window.dispatchEvent(new Event("tokenChanged"));
location.reload();
};
const items: HeaderItem[] = user
? [
{ label: "Inicio", page: "landing" },
{ label: "Consulta", page: "consulta" },
{ label: isCarga, page: "carga" },
...(user.tipo === "LOAD"
? [
{ label: "Carga individual", page: "carga individual" as PageKey },
]
: []),
{ label: "Cerrar sesión", onClick: handleLogout },
]
{ label: "Inicio", page: "landing" },
{ label: "Consulta", page: "consulta" },
...(user.tipo !== "EXTERNO"
? [{ label: isCarga, page: "carga" as PageKey }]
: []
),
...(user.tipo === "LOAD"
? [{ label: "Carga individual", page: "carga individual" as PageKey }]
: []),
...(user.tipo === "EXTERNO"
? [{ label: "Carga individual", page: "carga individual" as PageKey }]
: []
),
{ label: "Cerrar sesión", onClick: handleLogout },
]
: [
{ label: "Consulta", page: "consulta" },
{ label: "Inicio", page: "landing" },
{ label: "Login", page: "login" },
];
{ label: "Consulta", page: "consulta" },
{ label: "Inicio", page: "landing" },
{ label: "Login", page: "login" },
];
// 🔹 Esperamos hasta que se procese el token
if (!tokenProcessed) {
return <p>Cargando sesión...</p>;
}
return (
<>
<Header items={items} onClickItem={setPage} />
{/* Saludo en la esquina superior derecha */}
{saludo && (
<motion.div
className="w-full flex justify-end pr-6 mb-5"
@@ -88,17 +130,21 @@ const Home = () => {
>
<h2 className="text-dorado text-sm text-right">{saludo}</h2>
</motion.div>
)}
<div className="flex-grow-1 d-flex flex-column align-items-center justify-content-center text-center">
{page === "landing" && <LandingBody />}
{page === "login" && <LoginPage />}
{page === "consulta" && <Consulta />}
{page === "carga" && <Dashboard />}
{page === "carga individual" && user?.tipo === "LOAD" && (
<FormularioCargaIndividual />
{loading && <p>Cargando sesión...</p>}
{!loading && (
<>
{page === "landing" && <LandingBody />}
{page === "login" && <LoginPage />}
{page === "consulta" && <Consulta />}
{page === "carga" &&(user?.tipo!="EXTERNO")&& <Dashboard />}
{page === "carga individual" && (user?.tipo === "LOAD" || user?.tipo === "EXTERNO") && (
<FormularioCargaIndividual />
)}
</>
)}
</div>
+134
View File
@@ -0,0 +1,134 @@
"use client";
import SimpleInput from "@/components/input";
import "../landing/globals.css";
import Footer from "@/components/layout/footer";
import { motion } from "framer-motion";
import PasswordInput from "@/components/password";
import Button from "@/components/button";
import { useEffect, useState } from "react";
import axios from "axios";
const apiUrl = process.env.NEXT_PUBLIC_API_URL;
const SetPasswordPage = () => {
const [email, setEmail] = useState(""); // email desde URL
const [password, setPassword] = useState("");
const [confirmPassword, setConfirmPassword] = useState("");
const [loading, setLoading] = useState(false);
const [error, setError] = useState("");
// Captura email desde query param
useEffect(() => {
const params = new URLSearchParams(window.location.search);
const emailParam = params.get("email");
if (emailParam) setEmail(emailParam);
}, []);
const handleSetPassword = async () => {
if (!password || !confirmPassword) {
setError("Por favor ingresa la contraseña y confírmala");
return;
}
if (password !== confirmPassword) {
setError("Las contraseñas no coinciden");
return;
}
setLoading(true);
setError("");
try {
const response = await axios.post(`${apiUrl}/set-password`, {
email,
password,
});
const token = response.data.access_token;
localStorage.setItem("token", token);
window.dispatchEvent(new Event("tokenChanged"));
// Redirigir al Home
window.location.href = "/";
} catch (err: any) {
console.error(err);
setError(
err.response?.data?.message || "Error al establecer la contraseña"
);
} finally {
setLoading(false);
}
};
return (
<>
<motion.div
className="w-full flex justify-end pr-6 mb-5"
initial={{ x: -100, opacity: 0 }}
animate={{ x: 0, opacity: 1 }}
transition={{ duration: 1.5, ease: "easeOut" }}
>
</motion.div>
<div className="flex-grow-1 d-flex flex-column align-items-center justify-content-center text-center">
<motion.main
className="auth-page flex-fill flex-grow-1 d-flex flex-column justify-content-center align-items-center"
initial={{ y: 50, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
transition={{ delay: 0, duration: 0.5 }}
>
<h1 className="text-dorado">Establece tu contraseña para iniciar sesión</h1>
<SimpleInput
label="Usuario"
className={{
container: "w-300px mb-3",
}}
value={email}
readOnly
/>
<PasswordInput
label="Nueva contraseña"
className={{
container: "w-300px mb-3",
input: "form-control",
button: "btn btn-outline-secondary",
}}
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
<PasswordInput
label="Confirmar contraseña"
className={{
container: "w-300px mb-3",
input: "form-control",
button: "btn btn-outline-secondary",
}}
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
/>
{error && <p className="text-danger text-center mb-3">{error}</p>}
<div className="d-flex flex-column gap-3 w-300px">
<Button
onClick={handleSetPassword}
disabled={loading}
variant="azul"
>
{loading ? "Guardando..." : "Establecer contraseña"}
</Button>
</div>
</motion.main>
</div>
<Footer />
</>
);
};
export default SetPasswordPage;
+2 -2
View File
@@ -113,7 +113,7 @@ export default function Consulta() {
key={key}
style={{
padding: "1rem",
textAlign: "left",
textAlign: key === "fecha_activacion" ? "center" : "left",
borderBottom: "1px solid #ccc",
}}
>
@@ -136,7 +136,7 @@ export default function Consulta() {
textAlign: cellKey === "fecha_activacion" ? "center" : "left",
}}
>
{String(val)}
{cellKey === "fecha_activacion" && !val ? "" : String(val)}
</td>
);
})}
+27 -7
View File
@@ -8,6 +8,8 @@ const CargaArchivo: React.FC = () => {
const [archivo, setArchivo] = useState<File | null>(null);
const [errores, setErrores] = useState<string[] | null>(null);
const [loading, setLoading] = useState(false); // ← estado de carga
const [usarFuncionEspecial, setUsarFuncionEspecial] = useState(false);
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
@@ -39,13 +41,17 @@ const CargaArchivo: React.FC = () => {
setLoading(true); // ← empieza a subir
try {
const res = await fetch(`${apiUrl}/excel/load`, {
method: "POST",
headers: {
Authorization: `Bearer ${token}`,
},
body: formData,
});
const url = usarFuncionEspecial
? `${apiUrl}/excel/load?function=2`
: `${apiUrl}/excel/load`;
const res = await fetch(url, {
method: "POST",
headers: {
Authorization: `Bearer ${token}`,
},
body: formData,
});
const data = await res.json();
@@ -103,6 +109,20 @@ const CargaArchivo: React.FC = () => {
)}
</div>
<div className="mt-4 flex items-center justify-center gap-2">
<input
type="checkbox"
id="funcionEspecial"
checked={usarFuncionEspecial}
onChange={(e) => setUsarFuncionEspecial(e.target.checked)}
className="h-4 w-4 cursor-pointer"
/>
<label htmlFor="funcionEspecial" className="text-sm text-gray-700 cursor-pointer">
Usar función especial de carga
</label>
</div>
<div className="mt-4 text-center">
<Button onClick={handleUpload} disabled={!archivo || loading} variant="azul">
{loading ? "Subiendo..." : "Subir archivo"}
+49 -9
View File
@@ -1,5 +1,6 @@
"use client";
import Image from 'next/image';
import google from "@/img/google.png"
import Button from "@/components/button";
import SimpleInput from "@/components/input";
import PasswordInput from "@/components/password";
@@ -7,10 +8,10 @@ import React, { useState } from "react";
import axios from "axios";
import { useRouter } from "next/navigation";
import { motion } from "framer-motion";
import { image } from "framer-motion/client";
const apiUrl = process.env.NEXT_PUBLIC_API_URL;
export default function Page() {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
@@ -21,14 +22,11 @@ export default function Page() {
const handleLogin = async () => {
setLoading(true);
try {
const response = await axios.post(`${apiUrl}/login`, {
email: email,
password: password,
});
const token = response.data.access_token;
localStorage.setItem("token", token);
@@ -40,8 +38,6 @@ export default function Page() {
setPassword("");
setError("");
window.location.reload();
// Redirigir al usuario
} catch (err: any) {
const message =
"credenciales incorrectas, por favor verifica tu usuario y contraseña";
@@ -51,6 +47,18 @@ export default function Page() {
}
};
// 🔑 Captura Enter en cualquier input
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === "Enter") {
handleLogin();
}
};
// 👉 Iniciar sesión con Google
const handleGoogleLogin = () => {
window.location.href = `${apiUrl}/google`;
};
return (
<>
<motion.main
@@ -69,6 +77,7 @@ export default function Page() {
}}
value={email}
onChange={(e) => setEmail(e.target.value)}
onKeyDown={handleKeyDown}
/>
<PasswordInput
@@ -80,14 +89,45 @@ export default function Page() {
}}
value={password}
onChange={(e) => setPassword(e.target.value)}
onKeyDown={handleKeyDown}
/>
{error && <p className="text-danger text-center mb-3">{error}</p>}
<div className="d-flex gap-3">
<Button onClick={handleLogin} disabled={loading} variant="azul" >
<div className="d-flex flex-column gap-3 w-300px">
{/* Botón normal */}
<Button onClick={handleLogin} disabled={loading} variant="azul">
{loading ? "Iniciando sesión..." : "Iniciar Sesión"}
</Button>
<div> or </div>
{/* Botón Google */}
<div className="boton">
<Button
onClick={handleGoogleLogin}
variant="rojo"
>
{" "}
<Image src={google} alt="" className="imagen" />
<span>Continue with Google</span>
</Button>
</div>
{/* <Button
onClick={handleGoogleLogin}
variant="rojo"
className="d-flex align-items-center justify-content-center gap-2"
>
<img
src="https://www.svgrepo.com/show/355037/google.svg"
alt="Google"
className="w-5 h-5"
/>
Iniciar sesión con Google
</Button> */}
</div>
</motion.main>
</>
Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

+20 -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,20 @@
}
],
"paths": {
"@/*": ["./src/*"]
"@/*": [
"./src/*"
]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", "src/app/landing"],
"exclude": ["node_modules"]
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts",
"src/app/landing",
".next/dev/types/**/*.ts"
],
"exclude": [
"node_modules"
]
}
+42
View File
@@ -0,0 +1,42 @@
{
"compilerOptions": {
"target": "ES2017",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "react-jsx",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": [
"./src/*"
]
}
},
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts",
"src/app/landing",
".next/dev/types/**/*.ts"
],
"exclude": [
"node_modules"
]
}