From d6e4af90b9a3147211ff5d6ce6dc5c521a8d9902 Mon Sep 17 00:00:00 2001 From: Fimm Date: Wed, 14 Oct 2026 17:11:06 +0000 Subject: [PATCH] Se agrega servicio de APIREST y los endpoint para dashboard en conunto a funcionalidades de filtros. --- PLAN_API_SERVICES.md | 595 ++++++++++++++++++++++++ src/app/api/external/[...path]/route.ts | 78 ++++ src/app/excel.js | 31 ++ src/app/page.tsx | 206 +++++--- src/services/academico.ts | 26 ++ src/services/api.ts | 44 ++ src/services/cursos.ts | 41 ++ src/services/dashboard.ts | 14 + src/services/empresas.ts | 35 ++ src/services/informes.ts | 14 + src/services/secretaria.ts | 20 + src/services/types/academico.ts | 26 ++ src/services/types/cursos.ts | 36 ++ src/services/types/dashboard.ts | 55 +++ src/services/types/empresas.ts | 62 +++ src/services/types/informes.ts | 30 ++ src/services/types/secretaria.ts | 91 ++++ 17 files changed, 1348 insertions(+), 56 deletions(-) create mode 100644 PLAN_API_SERVICES.md create mode 100644 src/app/api/external/[...path]/route.ts create mode 100644 src/services/academico.ts create mode 100644 src/services/api.ts create mode 100644 src/services/cursos.ts create mode 100644 src/services/dashboard.ts create mode 100644 src/services/empresas.ts create mode 100644 src/services/informes.ts create mode 100644 src/services/secretaria.ts create mode 100644 src/services/types/academico.ts create mode 100644 src/services/types/cursos.ts create mode 100644 src/services/types/dashboard.ts create mode 100644 src/services/types/empresas.ts create mode 100644 src/services/types/informes.ts create mode 100644 src/services/types/secretaria.ts diff --git a/PLAN_API_SERVICES.md b/PLAN_API_SERVICES.md new file mode 100644 index 0000000..28fd6dd --- /dev/null +++ b/PLAN_API_SERVICES.md @@ -0,0 +1,595 @@ +# Plan: Implementación de Capa de Servicios API + +## Objetivo + +Crear la estructura de servicios API en `src/services/` para consumir una API externa, organizada por secciones del proyecto (Dashboard, Académico, Cursos, Empresas, Secretaría, Informes). Los archivos de servicio serán `.ts` puros (sin JSX) y se importarán desde cada `page.tsx` reemplazando los datos hardcodeados actuales. + +--- + +## Estructura de Directorios + +``` +src/ +├── services/ +│ ├── api.ts ← Cliente HTTP base +│ ├── types/ +│ │ ├── dashboard.ts +│ │ ├── academico.ts +│ │ ├── cursos.ts +│ │ ├── empresas.ts +│ │ ├── secretaria.ts +│ │ └── informes.ts +│ ├── dashboard.ts ← Servicios del Dashboard +│ ├── academico.ts ← Servicios de Académico +│ ├── cursos.ts ← Servicios de Cursos +│ ├── empresas.ts ← Servicios de Empresas +│ ├── secretaria.ts ← Servicios de Secretaría +│ └── informes.ts ← Servicios de Informes +``` + +**Total: 14 archivos nuevos** + +--- + +## 1. `src/services/api.ts` — Cliente HTTP Base + +Wrapper de `fetch` con manejo de errores, headers automáticos y métodos HTTP helper. + +```typescript +const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || ''; + +async function request(endpoint: string, options?: RequestInit): Promise { + const res = await fetch(`${API_BASE_URL}${endpoint}`, { + headers: { 'Content-Type': 'application/json', ...options?.headers }, + ...options, + }); + if (!res.ok) throw new Error(`API Error: ${res.status}`); + return res.json(); +} + +export const api = { + get: (url: string) => request(url), + post: (url: string, body: unknown) => request(url, { method: 'POST', body: JSON.stringify(body) }), + put: (url: string, body: unknown) => request(url, { method: 'PUT', body: JSON.stringify(body) }), + delete: (url: string) => request(url, { method: 'DELETE' }), +}; +``` + +--- + +## 2. `src/services/types/` — Interfaces TypeScript por Sección + +### `dashboard.ts` + +```typescript +export interface CursoDelDia { + id: number; + tipo: string; + curso: string; + profesor: string; + sede: string; + sala: string; + horario: string; + inicio: string; + termino: string; + sesion: string; +} + +export interface EstadisticasDashboard { + empresas: number; + cursos: number; + alumnosConSence: number; + alumnosSinSence: number; + total: number; +} +``` + +### `academico.ts` + +```typescript +export interface Programa { + nivel: string; + curso: string; + horasCronologicas: string; + cefrLevel: string; + libro: string; + senceOnline: string; + sencePresencial: string; +} + +export interface Libro { + id: string; + nombre: string; + nivel: string; +} + +export interface CodigoSence { + codigo: string; + descripcion: string; + tipo: string; +} + +export interface Ponderacion { + cursoId: string; + ponderaciones: { evaluacion: string; porcentaje: number }[]; +} +``` + +### `cursos.ts` + +```typescript +export interface CursoAdmin { + codigo: string; + nombre: string; + periodo: string; + fechaInicio: string; + fechaTermino: string; + sede: string; + estado: string; + sala: string; + profesor: string; + sesiones: number; + horario: string; +} + +export interface SesionCurso { + id: string; + fecha: string; + horaInicio: string; + horaFin: string; + estado: string; + profesor: string; +} + +export interface AsistenciaAlumno { + rut: string; + nombre: string; + asistio: boolean; + fecha: string; +} + +export interface NotaAlumno { + rut: string; + nombre: string; + nota: number; + evaluacion: string; +} +``` + +### `empresas.ts` + +```typescript +export interface EmpresaItem { + rut: string; + razonSocial: string; + giro: string; + correo: string; + ultimoContacto: string; +} + +export interface EmpresaDetalle { + rut: string; + razonSocial: string; + giro: string; + direccion: string; + comuna: string; + fono: string; + correo: string; + contacto: string; +} + +export interface CotizacionItem { + cotizacion: string; + contratosAsociados: string; + tipoCotizacion: string; + creacion: string; + estadoCotizacion: string; + valor: string; + idTipoCurso: string; + condicionCotizacion: string; + montoDescto: string; +} + +export interface ContratoItem { + contrato: string; + firmaEstado: number; + url: string; + cursoInfo: string; + valor: string; + autorizacionSSEE?: string; +} + +export interface DocumentoItem { + detalle: string; + numeroInterno?: string; + fechaSubida: string; + tipoDocumento?: string; + codInscripcion?: string; + responsable: string; +} + +export interface AlumnoItem { + rut: string; + nombre: string; + email: string; +} + +export interface ComprobanteOC { + detalle: string; + numeroInterno: string; + fechaSubida: string; + tipoDocumento: string; + responsable: string; +} +``` + +### `secretaria.ts` + +```typescript +export interface Alumno { + idAlumno: string; + AP_Paterno: string; + AP_Materno: string; + Nombres: string; +} + +export interface FichaAlumno { + idAlumno: string; + nombre: string; + paterno: string; + materno: string; + rut: string; + telefono: string; + email: string; + direccion: string; + comuna: string; + region: string; + fechaNacimiento: string; + ocupacion: string; + profesion: string; + cursos: CursoAlumno[]; + contratos: ContratoAlumno[]; + apoderados: ApoderadoAlumno[]; + certificados: CertificadoAlumno[]; + comentarios: ComentarioAlumno[]; +} + +export interface CursoAlumno { + year: number; + id: number; + curso: string; + contrato: string; + fechaInicio: string; + fechaTermino: string; + estado: string; +} + +export interface ContratoAlumno { /* campos por definir */ } +export interface ApoderadoAlumno { /* campos por definir */ } +export interface CertificadoAlumno { /* campos por definir */ } +export interface ComentarioAlumno { /* campos por definir */ } + +export interface FichaProfesor { + idProfesor: string; + nombre: string; + paterno: string; + materno: string; + rut: string; + telefono: string; + email: string; + cursos: CursoProfesor[]; +} + +export interface CursoProfesor { + codigo: string; + curso: string; + sede: string; + horario: string; + alumnos: number; +} + +export interface Evaluacion { + cursoId: string; + alumnoId: string; + tipo: string; + nota: number; + observaciones: string; +} +``` + +### `informes.ts` + +```typescript +export interface CursoItem { + Codigo: string; + Curso: string; + TipoCurso: string; + Periodo: string; + Titular: string; + Sede: string; + Alumnos: number; + Inicio: string; + Termino: string; + Schedule: string; + Estado: string; +} + +export interface MetricasInforme { + empresasTotal: number; + cursosTotal: number; + alumnosConSence: number; + alumnosSinSence: number; + alumnosTotal: number; +} + +export interface InformeAlumno { + rut: string; + nombre: string; + curso: string; + estado: string; + notas: number; + asistencia: number; +} +``` + +--- + +## 3. Servicios por Sección — Funciones API + +### `services/dashboard.ts` + +```typescript +import { api } from './api'; +import type { CursoDelDia, EstadisticasDashboard } from './types/dashboard'; + +export const obtenerCursosDelDia = (sede?: string, jornada?: string) => + api.get(`/api/dashboard/cursos-dia?sede=${sede || ''}&jornada=${jornada || ''}`); + +export const obtenerEstadisticas = () => + api.get('/api/dashboard/estadisticas'); +``` + +### `services/academico.ts` + +```typescript +import { api } from './api'; +import type { Programa, Libro, CodigoSence, Ponderacion } from './types/academico'; + +export const obtenerProgramas = () => + api.get('/api/academico/programas'); + +export const obtenerCursosPorPrograma = (programaId: string) => + api.get(`/api/academico/programas/${programaId}/cursos`); + +export const agregarCursoPrograma = (data: Omit) => + api.post('/api/academico/cursos', data); + +export const editarCursoPrograma = (id: string, data: Partial) => + api.put(`/api/academico/cursos/${id}`, data); + +export const obtenerLibros = () => + api.get('/api/academico/libros'); + +export const obtenerCodigosSence = (tipo: 'abiertos' | 'cerrados') => + api.get(`/api/academico/sence/${tipo}`); + +export const obtenerPonderaciones = (cursoId: string) => + api.get(`/api/academico/ponderaciones/${cursoId}`); + +export const guardarPonderaciones = (cursoId: string, data: Ponderacion) => + api.put(`/api/academico/ponderaciones/${cursoId}`, data); +``` + +### `services/cursos.ts` + +```typescript +import { api } from './api'; +import type { CursoAdmin, SesionCurso, AsistenciaAlumno, NotaAlumno } from './types/cursos'; + +export const obtenerCurso = (codigo: string) => + api.get(`/api/cursos/${codigo}`); + +export const editarFechasCurso = (codigo: string, data: { fechaInicio: string; fechaTermino: string }) => + api.put(`/api/cursos/${codigo}/fechas`, data); + +export const editarHorarioCurso = (codigo: string, data: { horario: string }) => + api.put(`/api/cursos/${codigo}/horario`, data); + +export const cambiarProfesor = (codigo: string, data: { profesorId: string }) => + api.put(`/api/cursos/${codigo}/profesor`, data); + +export const eliminarProfesor = (codigo: string) => + api.delete(`/api/cursos/${codigo}/profesor`); + +export const asignarReemplazo = (codigo: string, data: { profesorId: string; sesiones: string[] }) => + api.post(`/api/cursos/${codigo}/reemplazos`, data); + +export const obtenerSesiones = (codigo: string) => + api.get(`/api/cursos/${codigo}/sesiones`); + +export const reemplazarSesion = (sesionId: string, data: { nuevoProfesorId: string }) => + api.put(`/api/cursos/sesiones/${sesionId}/reemplazar`, data); + +export const suspenderSesion = (sesionId: string) => + api.put(`/api/cursos/sesiones/${sesionId}/suspender`); + +export const obtenerAsistencias = (codigo: string) => + api.get(`/api/cursos/${codigo}/asistencias`); + +export const actualizarAsistencia = (codigo: string, data: AsistenciaAlumno[]) => + api.put(`/api/cursos/${codigo}/asistencias`, data); + +export const obtenerNotas = (codigo: string) => + api.get(`/api/cursos/${codigo}/notas`); + +export const actualizarNotas = (codigo: string, data: NotaAlumno[]) => + api.put(`/api/cursos/${codigo}/notas`, data); +``` + +### `services/empresas.ts` + +```typescript +import { api } from './api'; +import type { + EmpresaItem, EmpresaDetalle, CotizacionItem, + ContratoItem, DocumentoItem, AlumnoItem, ComprobanteOC +} from './types/empresas'; + +export const obtenerEmpresas = () => + api.get('/api/empresas'); + +export const obtenerEmpresa = (rut: string) => + api.get(`/api/empresas/${rut}`); + +export const obtenerCotizaciones = (rutEmpresa: string) => + api.get(`/api/empresas/${rutEmpresa}/cotizaciones`); + +export const obtenerContratos = (cotizacionId: string) => + api.get(`/api/cotizaciones/${cotizacionId}/contratos`); + +export const verificarFirmaContrato = (contratoId: string) => + api.get<{ firmado: boolean }>(`/api/contratos/${contratoId}/firma`); + +export const obtenerDocumentos = (contratoId: string) => + api.get(`/api/contratos/${contratoId}/documentos`); + +export const subirDocumento = (contratoId: string, data: FormData) => + api.post(`/api/contratos/${contratoId}/documentos`, data); + +export const obtenerAlumnosContrato = (contratoId: string) => + api.get(`/api/contratos/${contratoId}/alumnos`); + +export const obtenerComprobantesOC = (contratoId: string) => + api.get(`/api/contratos/${contratoId}/comprobantes`); + +export const obtenerInscripcionesSNC = (contratoId: string) => + api.get(`/api/contratos/${contratoId}/inscripciones`); +``` + +### `services/secretaria.ts` + +```typescript +import { api } from './api'; +import type { Alumno, FichaAlumno, FichaProfesor, Evaluacion } from './types/secretaria'; + +export const buscarAlumno = (tipo: 'run' | 'nombre', texto: string) => + api.get(`/api/secretaria/alumnos?tipo=${tipo}&q=${texto}`); + +export const obtenerFichaAlumno = (idAlumno: string) => + api.get(`/api/secretaria/alumnos/${idAlumno}`); + +export const editarAlumno = (idAlumno: string, data: Partial) => + api.put(`/api/secretaria/alumnos/${idAlumno}`, data); + +export const cambiarCursoAlumno = (idAlumno: string, data: { cursoNuevoId: string }) => + api.put(`/api/secretaria/alumnos/${idAlumno}/curso`, data); + +export const obtenerFichaProfesor = (idProfesor: string) => + api.get(`/api/secretaria/profesores/${idProfesor}`); + +export const ingresarEvaluacion = (data: Evaluacion) => + api.post('/api/secretaria/evaluaciones', data); +``` + +### `services/informes.ts` + +```typescript +import { api } from './api'; +import type { CursoItem, MetricasInforme, InformeAlumno } from './types/informes'; + +export const obtenerCursosActivos = (mes: number, anio: number) => + api.get(`/api/informes/cursos-activos?mes=${mes}&anio=${anio}`); + +export const obtenerMetricas = (mes: number, anio: number) => + api.get(`/api/informes/metricas?mes=${mes}&anio=${anio}`); + +export const obtenerInformesAlumnos = (filtros?: { curso?: string; empresa?: string }) => + api.get('/api/informes/alumnos'); + +export const obtenerSesionesProfesor = (profesorId: string) => + api.get('/api/informes/sesiones-profesor'); +``` + +--- + +## 4. Consumo desde las Páginas (Antes vs Después) + +### ANTES (datos hardcodeados) +```typescript +const alumnos = [ + { id: 101, tipo: "Regular", curso: "Inglés Básico", profesor: "María López", ... } +]; +``` + +### DESPUÉS (usando servicio) +```typescript +import { obtenerCursosDelDia } from '@/services/dashboard'; +import type { CursoDelDia } from '@/services/types/dashboard'; + +const [cursos, setCursos] = useState([]); +const [loading, setLoading] = useState(true); + +useEffect(() => { + obtenerCursosDelDia('MONEDA', 'Diurno') + .then(setCursos) + .catch(console.error) + .finally(() => setLoading(false)); +}, []); +``` + +--- + +## 5. Resumen de Archivos + +| # | Archivo | Propósito | Funciones | +|---|---------|-----------|-----------| +| 1 | `services/api.ts` | Cliente HTTP base | 4 métodos | +| 2 | `services/types/dashboard.ts` | Interfaces Dashboard | 2 | +| 3 | `services/types/academico.ts` | Interfaces Académico | 4 | +| 4 | `services/types/cursos.ts` | Interfaces Cursos | 4 | +| 5 | `services/types/empresas.ts` | Interfaces Empresas | 7 | +| 6 | `services/types/secretaria.ts` | Interfaces Secretaría | 5 | +| 7 | `services/types/informes.ts` | Interfaces Informes | 3 | +| 8 | `services/dashboard.ts` | Funciones API Dashboard | 2 | +| 9 | `services/academico.ts` | Funciones API Académico | 8 | +| 10 | `services/cursos.ts` | Funciones API Cursos | 12 | +| 11 | `services/empresas.ts` | Funciones API Empresas | 10 | +| 12 | `services/secretaria.ts` | Funciones API Secretaría | 6 | +| 13 | `services/informes.ts` | Funciones API Informes | 4 | +| **Total** | **13 archivos** | | **~47 funciones** | + +--- + +## 6. Variables de Entorno Necesarias + +Agregar al archivo `.env.local`: + +``` +NEXT_PUBLIC_API_URL=https://tu-api.cl +``` + +--- + +## 7. Orden de Implementación Sugerido + +1. Crear `src/services/api.ts` (cliente HTTP base) +2. Crear `src/services/types/` (todas las interfaces) +3. Crear servicios por sección en orden de prioridad: + - `dashboard.ts` (más visible, primer impacto) + - `empresas.ts` (el más complejo, interfaces más definidas) + - `secretaria.ts` (búsqueda de alumnos, fichas) + - `cursos.ts` (administrador de cursos) + - `academico.ts` (programas, libros, SENCE) + - `informes.ts` (reportes) +4. Reemplazar datos mock en cada `page.tsx` correspondiente + +--- + +## 8. Notas Importantes + +- Todos los servicios son **archivos `.ts` puros** (sin JSX) +- Las interfaces están **separadas en `types/`** para reutilización +- El cliente HTTP maneja automáticamente `Content-Type: application/json` +- Las URLs de endpoints son **ejemplos** — ajustar según la API real +- Las interfaces pueden necesitar **campos adicionales** según la respuesta real de la API +- Las funciones de `subirDocumento` usan `FormData` para uploads de archivos diff --git a/src/app/api/external/[...path]/route.ts b/src/app/api/external/[...path]/route.ts new file mode 100644 index 0000000..2b18c2a --- /dev/null +++ b/src/app/api/external/[...path]/route.ts @@ -0,0 +1,78 @@ +import { NextRequest, NextResponse } from 'next/server'; + +const API_URL = process.env.API_URL || ''; +const API_KEY = process.env.API_KEY || ''; + +async function proxy(req: NextRequest, path: string[]) { + if (!API_URL) { + console.error('[Proxy] API_URL no está definida en .env.local'); + return NextResponse.json({ error: 'API_URL no configurada' }, { status: 500 }); + } + + const target = `${API_URL}/${path.join('/')}`; + const url = new URL(req.url); + const query = url.search; + + console.log('[Proxy] →', `${target}${query}`); + + try { + const headers: Record = { + 'X-Api-Key': API_KEY, + }; + const contentType = req.headers.get('content-type'); + if (contentType) headers['Content-Type'] = contentType; + + const init: RequestInit = { + method: req.method, + headers, + }; + if (req.method !== 'GET' && req.method !== 'HEAD') { + init.body = await req.text(); + } + + const res = await fetch(`${target}${query}`, init); + const text = await res.text(); + + console.log('[Proxy] ←', res.status, res.statusText); + + if (!res.ok) { + console.error('[Proxy] Respuesta no OK:', res.status, text); + return new NextResponse(text, { + status: res.status, + headers: { 'Content-Type': res.headers.get('Content-Type') || 'application/json' }, + }); + } + + return new NextResponse(text, { + status: res.status, + headers: { 'Content-Type': res.headers.get('Content-Type') || 'application/json' }, + }); + } catch (err) { + console.error('[Proxy] Error al llamar a la API externa:', target); + console.error('[Proxy] Detalle:', err); + return NextResponse.json( + { error: `Error al llamar a la API externa: ${err}` }, + { status: 502 } + ); + } +} + +export async function GET(req: NextRequest, { params }: { params: Promise<{ path: string[] }> }) { + const { path } = await params; + return proxy(req, path); +} + +export async function POST(req: NextRequest, { params }: { params: Promise<{ path: string[] }> }) { + const { path } = await params; + return proxy(req, path); +} + +export async function PUT(req: NextRequest, { params }: { params: Promise<{ path: string[] }> }) { + const { path } = await params; + return proxy(req, path); +} + +export async function DELETE(req: NextRequest, { params }: { params: Promise<{ path: string[] }> }) { + const { path } = await params; + return proxy(req, path); +} diff --git a/src/app/excel.js b/src/app/excel.js index db07919..d044e7a 100755 --- a/src/app/excel.js +++ b/src/app/excel.js @@ -32,5 +32,36 @@ export const descargarExcelProfesor = (dataArreglo, nombreArchivo = 'Reporte_Ses hojaDeTrabajo['!cols'] = columnasAncho; // 4. Descarga el archivo Excel .xlsx + XLSX.writeFile(libroDeTrabajo, `${nombreArchivo}.xlsx`); +}; + +/** + * Función genérica para descargar un arreglo de objetos como Excel + * @param {Array} dataArreglo - La constante con los datos + * @param {string} nombreArchivo - Nombre del archivo descargado (sin extensión) + * @param {string} nombreHoja - Nombre de la hoja dentro del libro + */ +export const descargarExcel = (dataArreglo, nombreArchivo = 'Reporte', nombreHoja = 'Datos') => { + if (!dataArreglo || dataArreglo.length === 0) { + console.warn("El arreglo de datos está vacío."); + return; + } + + const hojaDeTrabajo = XLSX.utils.json_to_sheet(dataArreglo); + + const libroDeTrabajo = XLSX.utils.book_new(); + XLSX.utils.book_append_sheet(libroDeTrabajo, hojaDeTrabajo, nombreHoja); + + const columnasAncho = Object.keys(dataArreglo[0]).map(key => { + const maxLen = dataArreglo.reduce((max, row) => { + const val = row[key] ? String(row[key]) : ''; + return Math.max(max, val.length); + }, key.length); + + return { wch: maxLen + 3 }; + }); + + hojaDeTrabajo['!cols'] = columnasAncho; + XLSX.writeFile(libroDeTrabajo, `${nombreArchivo}.xlsx`); }; \ No newline at end of file diff --git a/src/app/page.tsx b/src/app/page.tsx index 175483a..265d00f 100755 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,22 +1,67 @@ 'use client' import { useState, useEffect } from "react"; -import { Sun, Moon } from "@boxicons/react"; +import { Sun, Moon, Buildings, Book, UserCircle, ArrowFromTopStroke } from "@boxicons/react"; +import { obtenerCursosDelDia, obtenerKpiDashboard, obtenerDetalleCursoDia } from "@/services/dashboard"; +import type { CursoDelDia, KpiDashboard } from "@/services/types/dashboard"; +import { descargarExcel } from "../app/excel"; +import { color } from "chart.js/helpers"; export default function Home() { const [loading, setLoading] = useState(true); const [error, setError] = useState(""); + const [cursosDelDia, setCursosDelDia] = useState([]); + const [fecha, setFecha] = useState(() => new Date().toISOString().split('T')[0]); + const [Regleta, setRegleta] = useState(null); + const [sede, setSede] = useState(""); + const [jornada, setJornada] = useState(""); + + const toggleJornada = (valor: "1" | "2") => { + setJornada((prev) => (prev === valor ? "" : valor)); + }; + + const buscarCursos = (fechaSel: string, sedeSel: string, jornadaSel: string) => { + setLoading(true); + setError(""); + obtenerCursosDelDia(fechaSel, sedeSel, jornadaSel) + .then(setCursosDelDia) + .catch((err) => setError(err.message)) + .finally(() => setLoading(false)); + + obtenerKpiDashboard(fechaSel, sedeSel, jornadaSel) + .then(setRegleta) + .catch((err) => console.error("Error KPI:", err)); + }; + + const exportarDetalle = async () => { + setLoading(true); + setError(""); + try { + const data = await obtenerDetalleCursoDia(fecha, sede, jornada); + descargarExcel(data, `Detalle_Cursos_${fecha}`, "Detalle"); + } catch (err) { + setError(err instanceof Error ? err.message : String(err)); + } finally { + setLoading(false); + } + }; useEffect(() => { - setLoading(false); + buscarCursos(new Date().toISOString().split('T')[0], "", ""); }, []); - const alumnos = [ - { id: 101, tipo: "Regular", curso: "Inglés Básico Intensivo", profesor: "John Doe", sede: "Sede Central", sala: "Sala A", horario: "09:00 / 10:30", inicio: "09:00", inicioBadge: "bg-warning text-dark", termino: "10:30", sesion: "En proceso", sesionBadge: "bg-body-secondary text-body" }, - { id: 102, tipo: "Intensivo", curso: "Francés Avanzado", profesor: "Maria Perez", sede: "Sede Norte", sala: "Sala B", horario: "10:30 / 12:00", inicio: "10:30", inicioBadge: "bg-success text-white", termino: "12:00", sesion: "Completada", sesionBadge: "bg-success text-white" }, - { id: 103, tipo: "Regular", curso: "Alemán Intermedio", profesor: "Carlos López", sede: "Sede Sur", sala: "Sala C", horario: "14:00 / 15:30", inicio: "14:00", inicioBadge: "bg-warning text-dark", termino: "15:30", sesion: "En proceso", sesionBadge: "bg-body-secondary text-body" }, - { id: 104, tipo: "Intensivo", curso: "Inglés Avanzado", profesor: "Ana Torres", sede: "Sede Central", sala: "Sala A", horario: "16:00 / 17:30", inicio: "16:00", inicioBadge: "bg-danger text-white", termino: "17:30", sesion: "Pendiente", sesionBadge: "bg-danger text-white" }, - { id: 105, tipo: "Regular", curso: "Japonés Básico", profesor: "Takashi Tanaka", sede: "Sede Norte", sala: "Sala D", horario: "18:00 / 19:30", inicio: "18:00", inicioBadge: "bg-warning text-dark", termino: "19:30", sesion: "En proceso", sesionBadge: "bg-body-secondary text-body" }, - ]; + const badgeSesion = (sesion: string | null) => { + if (!sesion) return "bg-body-secondary text-body"; + const s = sesion.toLowerCase(); + if (s.includes("completada") || s.includes("finalizada")) return "bg-success text-white"; + if (s.includes("pendiente")) return "bg-danger text-white"; + return "bg-body-secondary text-body"; + }; + + const badgeInicio = (inicio: string | null) => { + if (!inicio) return "bg-body-secondary text-body"; + if (inicio.toLowerCase().includes("pendiente")) return "bg-warning text-dark"; + return "bg-success text-white"; + }; if (loading) return
Cargando...
; if (error) return
{error}
; @@ -37,21 +82,38 @@ export default function Home() {
- + setFecha(e.target.value)} + />
- setSede(e.target.value)} + > + + + + + +
- +
@@ -59,7 +121,12 @@ export default function Home() {
-
-
@@ -84,55 +157,55 @@ export default function Home() {
-
+
-

+
Empresas
- Total -
0 %
+ Total +
{Regleta?.cantidad_empresas ?? 0}
-
+
-

+
Cursos
- Total -
0 %
+ Total +
{Regleta?.cantidad_cursos ?? 0}
-
+
-

+
Alumnos
- Con SENCE -
0
+ Con SENCE +
{Regleta?.cantidad_alumnos_sence ?? 0}
- Sin SENCE -
0
+ Sin SENCE +
{Regleta?.cantidad_alumnos_nosence ?? 0}
- Total -
0
+ Total +
{Regleta?.cantidad_alumnos ?? 0}
@@ -141,8 +214,13 @@ export default function Home() {
-
@@ -169,24 +247,40 @@ export default function Home() { - {alumnos.map((a) => ( - - {a.id} - {a.tipo} - {a.curso} - {a.profesor} - {a.sede} - {a.sala} - {a.horario} - - {a.inicio} - - {a.termino} - - {a.sesion} + {cursosDelDia.length === 0 ? ( + + + Sin cursos registrados - ))} + ) : ( + cursosDelDia.map((a) => ( + + {a.codigo} + {a.tipoCurso ?? "—"} + {a.curso ?? "—"} + {a.profesor ?? "—"} + {a.sede ?? "—"} + {a.sala ?? "—"} + + {a.horaInicio && a.horaTermino + ? `${a.horaInicio} / ${a.horaTermino}` + : "—"} + + + + {a.inicioSesion ?? "—"} + + + {a.terminoSesion ?? "—"} + + + {a.sesionRegistrada ?? "—"} + + + + )) + )}
diff --git a/src/services/academico.ts b/src/services/academico.ts new file mode 100644 index 0000000..7621018 --- /dev/null +++ b/src/services/academico.ts @@ -0,0 +1,26 @@ +import { api } from './api'; +import type { Programa, Libro, CodigoSence, Ponderacion } from './types/academico'; + +export const obtenerProgramas = () => + api.get('/api/academico/programas'); + +export const obtenerCursosPorPrograma = (programaId: string) => + api.get(`/api/academico/programas/${programaId}/cursos`); + +export const agregarCursoPrograma = (data: Omit) => + api.post('/api/academico/cursos', data); + +export const editarCursoPrograma = (id: string, data: Partial) => + api.put(`/api/academico/cursos/${id}`, data); + +export const obtenerLibros = () => + api.get('/api/academico/libros'); + +export const obtenerCodigosSence = (tipo: 'abiertos' | 'cerrados') => + api.get(`/api/academico/sence/${tipo}`); + +export const obtenerPonderaciones = (cursoId: string) => + api.get(`/api/academico/ponderaciones/${cursoId}`); + +export const guardarPonderaciones = (cursoId: string, data: Ponderacion) => + api.put(`/api/academico/ponderaciones/${cursoId}`, data); diff --git a/src/services/api.ts b/src/services/api.ts new file mode 100644 index 0000000..e217ce7 --- /dev/null +++ b/src/services/api.ts @@ -0,0 +1,44 @@ +const PROXY_BASE = '/api/external'; + +async function request(endpoint: string, options?: RequestInit): Promise { + const url = `${PROXY_BASE}${endpoint}`; + + console.log('[API] →', url); + + let res: Response; + try { + res = await fetch(url, { + headers: { + 'Content-Type': 'application/json', + ...options?.headers, + }, + ...options, + }); + } catch (networkError) { + console.error('[API] Error de red al llamar:', url); + console.error('[API] Detalle:', networkError); + throw new Error(`Fallo de red al llamar ${url}: ${networkError}`); + } + + if (!res.ok) { + const body = await res.text().catch(() => '(sin body)'); + console.error('[API] Respuesta no OK:', res.status, res.statusText); + console.error('[API] Body:', body); + throw new Error(`API Error: ${res.status} ${res.statusText} - ${body}`); + } + + try { + return await res.json(); + } catch (parseError) { + console.error('[API] Error al parsear JSON de:', url); + console.error('[API] Detalle:', parseError); + throw new Error(`Respuesta no válida JSON de ${url}`); + } +} + +export const api = { + get: (url: string) => request(url), + post: (url: string, body: unknown) => request(url, { method: 'POST', body: JSON.stringify(body) }), + put: (url: string, body: unknown) => request(url, { method: 'PUT', body: JSON.stringify(body) }), + delete: (url: string) => request(url, { method: 'DELETE' }), +}; diff --git a/src/services/cursos.ts b/src/services/cursos.ts new file mode 100644 index 0000000..56b5836 --- /dev/null +++ b/src/services/cursos.ts @@ -0,0 +1,41 @@ +import { api } from './api'; +import type { CursoAdmin, SesionCurso, AsistenciaAlumno, NotaAlumno } from './types/cursos'; + +export const obtenerCurso = (codigo: string) => + api.get(`/api/cursos/${codigo}`); + +export const editarFechasCurso = (codigo: string, data: { fechaInicio: string; fechaTermino: string }) => + api.put(`/api/cursos/${codigo}/fechas`, data); + +export const editarHorarioCurso = (codigo: string, data: { horario: string }) => + api.put(`/api/cursos/${codigo}/horario`, data); + +export const cambiarProfesor = (codigo: string, data: { profesorId: string }) => + api.put(`/api/cursos/${codigo}/profesor`, data); + +export const eliminarProfesor = (codigo: string) => + api.delete(`/api/cursos/${codigo}/profesor`); + +export const asignarReemplazo = (codigo: string, data: { profesorId: string; sesiones: string[] }) => + api.post(`/api/cursos/${codigo}/reemplazos`, data); + +export const obtenerSesiones = (codigo: string) => + api.get(`/api/cursos/${codigo}/sesiones`); + +export const reemplazarSesion = (sesionId: string, data: { nuevoProfesorId: string }) => + api.put(`/api/cursos/sesiones/${sesionId}/reemplazar`, data); + +export const suspenderSesion = (sesionId: string) => + api.put(`/api/cursos/sesiones/${sesionId}/suspender`); + +export const obtenerAsistencias = (codigo: string) => + api.get(`/api/cursos/${codigo}/asistencias`); + +export const actualizarAsistencia = (codigo: string, data: AsistenciaAlumno[]) => + api.put(`/api/cursos/${codigo}/asistencias`, data); + +export const obtenerNotas = (codigo: string) => + api.get(`/api/cursos/${codigo}/notas`); + +export const actualizarNotas = (codigo: string, data: NotaAlumno[]) => + api.put(`/api/cursos/${codigo}/notas`, data); diff --git a/src/services/dashboard.ts b/src/services/dashboard.ts new file mode 100644 index 0000000..29320af --- /dev/null +++ b/src/services/dashboard.ts @@ -0,0 +1,14 @@ +import { api } from './api'; +import type { CursoDelDia, EstadisticasDashboard, KpiDashboard, DetalleCursoDia } from './types/dashboard'; + +export const obtenerCursosDelDia = (fecha: string, idsede?: string, idjornada?: string) => + api.get(`/api/empresas-cursos?fecha=${fecha}&idsede=${idsede ?? ''}&idjornada=${idjornada ?? ''}`); + +export const obtenerEstadisticas = () => + api.get('/api/dashboard/estadisticas'); + +export const obtenerKpiDashboard = (fecha: string, idsede?: string, idjornada?: string) => + api.get(`/api/empresas-cursos/kpi-dashboard?fecha=${fecha}&idsede=${idsede ?? ''}&idjornada=${idjornada ?? ''}`); + +export const obtenerDetalleCursoDia = (fecha: string, idsede?: string, idjornada?: string) => + api.get(`/api/empresas-cursos/detalle?fecha=${fecha}&idsede=${idsede ?? ''}&idjornada=${idjornada ?? ''}`); diff --git a/src/services/empresas.ts b/src/services/empresas.ts new file mode 100644 index 0000000..4e262f0 --- /dev/null +++ b/src/services/empresas.ts @@ -0,0 +1,35 @@ +import { api } from './api'; +import type { + EmpresaItem, EmpresaDetalle, CotizacionItem, + ContratoItem, DocumentoItem, AlumnoItem, ComprobanteOC +} from './types/empresas'; + +export const obtenerEmpresas = () => + api.get('/api/empresas'); + +export const obtenerEmpresa = (rut: string) => + api.get(`/api/empresas/${rut}`); + +export const obtenerCotizaciones = (rutEmpresa: string) => + api.get(`/api/empresas/${rutEmpresa}/cotizaciones`); + +export const obtenerContratos = (cotizacionId: string) => + api.get(`/api/cotizaciones/${cotizacionId}/contratos`); + +export const verificarFirmaContrato = (contratoId: string) => + api.get<{ firmado: boolean }>(`/api/contratos/${contratoId}/firma`); + +export const obtenerDocumentos = (contratoId: string) => + api.get(`/api/contratos/${contratoId}/documentos`); + +export const subirDocumento = (contratoId: string, data: FormData) => + api.post(`/api/contratos/${contratoId}/documentos`, data); + +export const obtenerAlumnosContrato = (contratoId: string) => + api.get(`/api/contratos/${contratoId}/alumnos`); + +export const obtenerComprobantesOC = (contratoId: string) => + api.get(`/api/contratos/${contratoId}/comprobantes`); + +export const obtenerInscripcionesSNC = (contratoId: string) => + api.get(`/api/contratos/${contratoId}/inscripciones`); diff --git a/src/services/informes.ts b/src/services/informes.ts new file mode 100644 index 0000000..0c1590c --- /dev/null +++ b/src/services/informes.ts @@ -0,0 +1,14 @@ +import { api } from './api'; +import type { CursoItem, MetricasInforme, InformeAlumno } from './types/informes'; + +export const obtenerCursosActivos = (mes: number, anio: number) => + api.get(`/api/informes/cursos-activos?mes=${mes}&anio=${anio}`); + +export const obtenerMetricas = (mes: number, anio: number) => + api.get(`/api/informes/metricas?mes=${mes}&anio=${anio}`); + +export const obtenerInformesAlumnos = (filtros?: { curso?: string; empresa?: string }) => + api.get('/api/informes/alumnos'); + +export const obtenerSesionesProfesor = (profesorId: string) => + api.get('/api/informes/sesiones-profesor'); diff --git a/src/services/secretaria.ts b/src/services/secretaria.ts new file mode 100644 index 0000000..5c7a995 --- /dev/null +++ b/src/services/secretaria.ts @@ -0,0 +1,20 @@ +import { api } from './api'; +import type { Alumno, FichaAlumno, FichaProfesor, Evaluacion } from './types/secretaria'; + +export const buscarAlumno = (tipo: 'run' | 'nombre', texto: string) => + api.get(`/api/secretaria/alumnos?tipo=${tipo}&q=${texto}`); + +export const obtenerFichaAlumno = (idAlumno: string) => + api.get(`/api/secretaria/alumnos/${idAlumno}`); + +export const editarAlumno = (idAlumno: string, data: Partial) => + api.put(`/api/secretaria/alumnos/${idAlumno}`, data); + +export const cambiarCursoAlumno = (idAlumno: string, data: { cursoNuevoId: string }) => + api.put(`/api/secretaria/alumnos/${idAlumno}/curso`, data); + +export const obtenerFichaProfesor = (idProfesor: string) => + api.get(`/api/secretaria/profesores/${idProfesor}`); + +export const ingresarEvaluacion = (data: Evaluacion) => + api.post('/api/secretaria/evaluaciones', data); diff --git a/src/services/types/academico.ts b/src/services/types/academico.ts new file mode 100644 index 0000000..cf1c3fa --- /dev/null +++ b/src/services/types/academico.ts @@ -0,0 +1,26 @@ +export interface Programa { + nivel: string; + curso: string; + horasCronologicas: string; + cefrLevel: string; + libro: string; + senceOnline: string; + sencePresencial: string; +} + +export interface Libro { + id: string; + nombre: string; + nivel: string; +} + +export interface CodigoSence { + codigo: string; + descripcion: string; + tipo: string; +} + +export interface Ponderacion { + cursoId: string; + ponderaciones: { evaluacion: string; porcentaje: number }[]; +} diff --git a/src/services/types/cursos.ts b/src/services/types/cursos.ts new file mode 100644 index 0000000..fb3e7ae --- /dev/null +++ b/src/services/types/cursos.ts @@ -0,0 +1,36 @@ +export interface CursoAdmin { + codigo: string; + nombre: string; + periodo: string; + fechaInicio: string; + fechaTermino: string; + sede: string; + estado: string; + sala: string; + profesor: string; + sesiones: number; + horario: string; +} + +export interface SesionCurso { + id: string; + fecha: string; + horaInicio: string; + horaFin: string; + estado: string; + profesor: string; +} + +export interface AsistenciaAlumno { + rut: string; + nombre: string; + asistio: boolean; + fecha: string; +} + +export interface NotaAlumno { + rut: string; + nombre: string; + nota: number; + evaluacion: string; +} diff --git a/src/services/types/dashboard.ts b/src/services/types/dashboard.ts new file mode 100644 index 0000000..8127faa --- /dev/null +++ b/src/services/types/dashboard.ts @@ -0,0 +1,55 @@ +export interface CursoDelDia { + codigo: number; + tipoCurso: string | null; + curso: string | null; + profesor: string | null; + asignacion: string | null; + sede: string | null; + periodo: string | null; + sala: string | null; + jornada: string | null; + horaInicio: string | null; + inicio: string | null; + horaTermino: string | null; + inicioSesion: string | null; + terminoSesion: string | null; + sesionRegistrada: string | null; +} + +export interface EstadisticasDashboard { + empresas: number; + cursos: number; + alumnosConSence: number; + alumnosSinSence: number; + total: number; +} + +export interface KpiDashboard { + cantidad_empresas: number; + cantidad_cursos: number; + cantidad_alumnos: number; + cantidad_alumnos_sence: number; + cantidad_alumnos_nosence: number; +} + +export interface DetalleCursoDia { + codigo: number; + TipoCurso: string | null; + curso: string | null; + idAlumno: string | null; + idtiporegistroacademico: number | null; + contrato: number | null; + idEmpresas: string | null; + rznscl: string | null; + profesor: string | null; + asignacion: string | null; + sede: string | null; + periodo: string | null; + sala: string | null; + jornada: string | null; + Hora_Inicio: string | null; + Hora_Termino: string | null; + inicio: string | null; + termino: string | null; + SesionRegistrada: string | null; +} diff --git a/src/services/types/empresas.ts b/src/services/types/empresas.ts new file mode 100644 index 0000000..93ca96f --- /dev/null +++ b/src/services/types/empresas.ts @@ -0,0 +1,62 @@ +export interface EmpresaItem { + rut: string; + razonSocial: string; + giro: string; + correo: string; + ultimoContacto: string; +} + +export interface EmpresaDetalle { + rut: string; + razonSocial: string; + giro: string; + direccion: string; + comuna: string; + fono: string; + correo: string; + contacto: string; +} + +export interface CotizacionItem { + cotizacion: string; + contratosAsociados: string; + tipoCotizacion: string; + creacion: string; + estadoCotizacion: string; + valor: string; + idTipoCurso: string; + condicionCotizacion: string; + montoDescto: string; +} + +export interface ContratoItem { + contrato: string; + firmaEstado: number; + url: string; + cursoInfo: string; + valor: string; + autorizacionSSEE?: string; +} + +export interface DocumentoItem { + detalle: string; + numeroInterno?: string; + fechaSubida: string; + tipoDocumento?: string; + codInscripcion?: string; + responsable: string; +} + +export interface AlumnoItem { + rut: string; + nombre: string; + email: string; +} + +export interface ComprobanteOC { + detalle: string; + numeroInterno: string; + fechaSubida: string; + tipoDocumento: string; + responsable: string; +} diff --git a/src/services/types/informes.ts b/src/services/types/informes.ts new file mode 100644 index 0000000..6df499c --- /dev/null +++ b/src/services/types/informes.ts @@ -0,0 +1,30 @@ +export interface CursoItem { + Codigo: string; + Curso: string; + TipoCurso: string; + Periodo: string; + Titular: string; + Sede: string; + Alumnos: number; + Inicio: string; + Termino: string; + Schedule: string; + Estado: string; +} + +export interface MetricasInforme { + empresasTotal: number; + cursosTotal: number; + alumnosConSence: number; + alumnosSinSence: number; + alumnosTotal: number; +} + +export interface InformeAlumno { + rut: string; + nombre: string; + curso: string; + estado: string; + notas: number; + asistencia: number; +} diff --git a/src/services/types/secretaria.ts b/src/services/types/secretaria.ts new file mode 100644 index 0000000..675d466 --- /dev/null +++ b/src/services/types/secretaria.ts @@ -0,0 +1,91 @@ +export interface Alumno { + idAlumno: string; + AP_Paterno: string; + AP_Materno: string; + Nombres: string; +} + +export interface FichaAlumno { + idAlumno: string; + nombre: string; + paterno: string; + materno: string; + rut: string; + telefono: string; + email: string; + direccion: string; + comuna: string; + region: string; + fechaNacimiento: string; + ocupacion: string; + profesion: string; + cursos: CursoAlumno[]; + contratos: ContratoAlumno[]; + apoderados: ApoderadoAlumno[]; + certificados: CertificadoAlumno[]; + comentarios: ComentarioAlumno[]; +} + +export interface CursoAlumno { + year: number; + id: number; + curso: string; + contrato: string; + fechaInicio: string; + fechaTermino: string; + estado: string; +} + +export interface ContratoAlumno { + id: string; + numero: string; + estado: string; +} + +export interface ApoderadoAlumno { + rut: string; + nombre: string; + telefono: string; + parentesco: string; +} + +export interface CertificadoAlumno { + id: string; + fechaEmision: string; + curso: string; + horas: number; +} + +export interface ComentarioAlumno { + id: string; + fecha: string; + autor: string; + texto: string; +} + +export interface FichaProfesor { + idProfesor: string; + nombre: string; + paterno: string; + materno: string; + rut: string; + telefono: string; + email: string; + cursos: CursoProfesor[]; +} + +export interface CursoProfesor { + codigo: string; + curso: string; + sede: string; + horario: string; + alumnos: number; +} + +export interface Evaluacion { + cursoId: string; + alumnoId: string; + tipo: string; + nota: number; + observaciones: string; +}