3 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
10 changed files with 242 additions and 30 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',
},
},
];
@@ -30,6 +30,33 @@ export default function EditarCasoEspecial() {
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>;
}
@@ -100,29 +127,6 @@ export default function EditarCasoEspecial() {
const updateIsLoading = (value: boolean) => setIsLoading(value);
useEffect(() => {
const fetchUser = async () => {
await getLocalInfo();
if (!idCasoEspecial) return
try {
setIsLoading(true);
const data = await getUserCasoEspecial(idCasoEspecial)
setDatos(data);
} catch (error) {
const axiosErr = error as AxiosError<any>;
const mensaje = axiosErr?.response?.data?.message || 'No se pudo obtener la infromacion del caso especial.'
Swal.fire({
title: 'Error',
icon: 'error',
text: mensaje,
})
}
}
}, [])
return (
<section className="container px-2 pb-5">
<BotonRegresar />
+3
View File
@@ -2,6 +2,7 @@
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";
@@ -46,6 +47,8 @@ export default function Registro() {
<GustavoBazPrada
years={years}
/>
<ReporteCasoEspecial />
</section>
);
}
-1
View File
@@ -3,7 +3,6 @@ import { axiosInstance } from "@/api/config";
import Image from "next/image";
import { useRouter } from "next/navigation";
import React, { useState } from "react";
import { toast } from "react-toastify";
import Swal from "sweetalert2";
export default function Home() {
+28 -3
View File
@@ -20,7 +20,32 @@ export default function Cuestionario({ years, admin, updateIsLoading }: Props) {
try {
//updateIsLoading(true);
//const res = await axiosInstance.get(`/cuestionario_alumno?year=${selectedCuestionario}&version=${version}`, {
const res = await axiosInstance.get(`/cuestionario-alumno2`, {
// 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: { anio: selectedYear, version },
responseType: "blob",
});
@@ -85,8 +110,8 @@ export default function Cuestionario({ years, admin, updateIsLoading }: Props) {
<InputGroup>
<FormSelect onChange={(e) => setSelectedCuestionario(e.target.value)}>
<option value="">Seleccione un cuestionario:</option>
<option value="cuestionario_alumno">Cuestionario de Alumnos</option>
<option value="cuestionario_programa">Cuestionario de Programas</option>
<option value="cuestionario-alumno2">Cuestionario de Alumnos</option>
<option value="cuestionario-programa2">Cuestionario de Programas</option>
</FormSelect>
</InputGroup>
</FormGroup>
@@ -6,7 +6,6 @@ import { isAxiosError } from "axios";
import validator from "validator";
import { useRouter } from "next/navigation";
import Swal from "sweetalert2";
import { toast } from "react-toastify";
interface Responsable {
idUsuario?: number;
@@ -47,6 +47,7 @@ interface Datos {
fechaLiberacion?: string;
telefono?: string;
direccion?: string;
responsableIntermo?: string;
/*
cartaAceptacion?: boolean;
cartaTermino?: boolean;
@@ -145,7 +146,7 @@ export default function InformacionServicio({ admin, imprimirError, updateIsLoad
</div>
<div className="mb-3">
<label className="form-label fw-semibold">Programa:</label>
<label className="form-label fw-semibold">Programa interno:</label>
<p className="form-control">{datos.programa?.programa}</p>
</div>
@@ -159,6 +160,11 @@ export default function InformacionServicio({ admin, imprimirError, updateIsLoad
<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>
@@ -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>
)
}
+1 -1
View File
@@ -205,7 +205,7 @@ Codigo anterior para el envio del archivo
{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">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">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