16 KiB
16 KiB
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.
const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || '';
async function request<T>(endpoint: string, options?: RequestInit): Promise<T> {
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: <T>(url: string) => request<T>(url),
post: <T>(url: string, body: unknown) => request<T>(url, { method: 'POST', body: JSON.stringify(body) }),
put: <T>(url: string, body: unknown) => request<T>(url, { method: 'PUT', body: JSON.stringify(body) }),
delete: <T>(url: string) => request<T>(url, { method: 'DELETE' }),
};
2. src/services/types/ — Interfaces TypeScript por Sección
dashboard.ts
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
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
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
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
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
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
import { api } from './api';
import type { CursoDelDia, EstadisticasDashboard } from './types/dashboard';
export const obtenerCursosDelDia = (sede?: string, jornada?: string) =>
api.get<CursoDelDia[]>(`/api/dashboard/cursos-dia?sede=${sede || ''}&jornada=${jornada || ''}`);
export const obtenerEstadisticas = () =>
api.get<EstadisticasDashboard>('/api/dashboard/estadisticas');
services/academico.ts
import { api } from './api';
import type { Programa, Libro, CodigoSence, Ponderacion } from './types/academico';
export const obtenerProgramas = () =>
api.get<Programa[]>('/api/academico/programas');
export const obtenerCursosPorPrograma = (programaId: string) =>
api.get<Programa[]>(`/api/academico/programas/${programaId}/cursos`);
export const agregarCursoPrograma = (data: Omit<Programa, 'id'>) =>
api.post('/api/academico/cursos', data);
export const editarCursoPrograma = (id: string, data: Partial<Programa>) =>
api.put(`/api/academico/cursos/${id}`, data);
export const obtenerLibros = () =>
api.get<Libro[]>('/api/academico/libros');
export const obtenerCodigosSence = (tipo: 'abiertos' | 'cerrados') =>
api.get<CodigoSence[]>(`/api/academico/sence/${tipo}`);
export const obtenerPonderaciones = (cursoId: string) =>
api.get<Ponderacion>(`/api/academico/ponderaciones/${cursoId}`);
export const guardarPonderaciones = (cursoId: string, data: Ponderacion) =>
api.put(`/api/academico/ponderaciones/${cursoId}`, data);
services/cursos.ts
import { api } from './api';
import type { CursoAdmin, SesionCurso, AsistenciaAlumno, NotaAlumno } from './types/cursos';
export const obtenerCurso = (codigo: string) =>
api.get<CursoAdmin>(`/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<SesionCurso[]>(`/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<AsistenciaAlumno[]>(`/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<NotaAlumno[]>(`/api/cursos/${codigo}/notas`);
export const actualizarNotas = (codigo: string, data: NotaAlumno[]) =>
api.put(`/api/cursos/${codigo}/notas`, data);
services/empresas.ts
import { api } from './api';
import type {
EmpresaItem, EmpresaDetalle, CotizacionItem,
ContratoItem, DocumentoItem, AlumnoItem, ComprobanteOC
} from './types/empresas';
export const obtenerEmpresas = () =>
api.get<EmpresaItem[]>('/api/empresas');
export const obtenerEmpresa = (rut: string) =>
api.get<EmpresaDetalle>(`/api/empresas/${rut}`);
export const obtenerCotizaciones = (rutEmpresa: string) =>
api.get<CotizacionItem[]>(`/api/empresas/${rutEmpresa}/cotizaciones`);
export const obtenerContratos = (cotizacionId: string) =>
api.get<ContratoItem[]>(`/api/cotizaciones/${cotizacionId}/contratos`);
export const verificarFirmaContrato = (contratoId: string) =>
api.get<{ firmado: boolean }>(`/api/contratos/${contratoId}/firma`);
export const obtenerDocumentos = (contratoId: string) =>
api.get<DocumentoItem[]>(`/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<AlumnoItem[]>(`/api/contratos/${contratoId}/alumnos`);
export const obtenerComprobantesOC = (contratoId: string) =>
api.get<ComprobanteOC[]>(`/api/contratos/${contratoId}/comprobantes`);
export const obtenerInscripcionesSNC = (contratoId: string) =>
api.get<DocumentoItem[]>(`/api/contratos/${contratoId}/inscripciones`);
services/secretaria.ts
import { api } from './api';
import type { Alumno, FichaAlumno, FichaProfesor, Evaluacion } from './types/secretaria';
export const buscarAlumno = (tipo: 'run' | 'nombre', texto: string) =>
api.get<Alumno[]>(`/api/secretaria/alumnos?tipo=${tipo}&q=${texto}`);
export const obtenerFichaAlumno = (idAlumno: string) =>
api.get<FichaAlumno>(`/api/secretaria/alumnos/${idAlumno}`);
export const editarAlumno = (idAlumno: string, data: Partial<FichaAlumno>) =>
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<FichaProfesor>(`/api/secretaria/profesores/${idProfesor}`);
export const ingresarEvaluacion = (data: Evaluacion) =>
api.post('/api/secretaria/evaluaciones', data);
services/informes.ts
import { api } from './api';
import type { CursoItem, MetricasInforme, InformeAlumno } from './types/informes';
export const obtenerCursosActivos = (mes: number, anio: number) =>
api.get<CursoItem[]>(`/api/informes/cursos-activos?mes=${mes}&anio=${anio}`);
export const obtenerMetricas = (mes: number, anio: number) =>
api.get<MetricasInforme>(`/api/informes/metricas?mes=${mes}&anio=${anio}`);
export const obtenerInformesAlumnos = (filtros?: { curso?: string; empresa?: string }) =>
api.get<InformeAlumno[]>('/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)
const alumnos = [
{ id: 101, tipo: "Regular", curso: "Inglés Básico", profesor: "María López", ... }
];
DESPUÉS (usando servicio)
import { obtenerCursosDelDia } from '@/services/dashboard';
import type { CursoDelDia } from '@/services/types/dashboard';
const [cursos, setCursos] = useState<CursoDelDia[]>([]);
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
- Crear
src/services/api.ts(cliente HTTP base) - Crear
src/services/types/(todas las interfaces) - 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)
- Reemplazar datos mock en cada
page.tsxcorrespondiente
8. Notas Importantes
- Todos los servicios son archivos
.tspuros (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
subirDocumentousanFormDatapara uploads de archivos