|
|
# Módulo 4 — Uso de Servicios y API REST
|
|
# Módulo 4 — Uso de Servicios y API REST en TypeScript
|
|
|
|
|
|
|
|
**Aplicación práctica: Conexión del sistema de gestión sanitaria con una API REST propia (consulta y actualización de recursos y personal).**
|
|
**Aplicación práctica: Conexión del sistema de gestión sanitaria con una API REST propia (consulta y actualización de recursos y personal).**
|
|
|
|
|
|
| ... | @@ -6,84 +6,86 @@ |
... | @@ -6,84 +6,86 @@ |
|
|
|
|
|
|
|
## 1. Introducción a la asincronía
|
|
## 1. Introducción a la asincronía
|
|
|
|
|
|
|
|
JavaScript es un lenguaje **asíncrono y no bloqueante**, lo que significa que **puede ejecutar tareas sin detener el flujo principal** (por ejemplo, esperar la respuesta de un servidor mientras sigue procesando otras acciones).
|
|
TypeScript hereda el modelo **asíncrono y no bloqueante** de JavaScript, pero añade **tipado estático** que permite detectar errores en el manejo de promesas y datos remotos antes de ejecutar la aplicación.
|
|
|
|
|
|
|
|
Esto es posible gracias al **Event Loop** y a las **promesas (Promises)**, que permiten manejar operaciones que tardan tiempo, como peticiones a una API o lectura de archivos.
|
|
```ts
|
|
|
|
console.log('Iniciando petición...');
|
|
|
|
|
|
|
|
```javascript
|
|
setTimeout((): void => {
|
|
|
console.log("Iniciando petición...");
|
|
console.log('Datos recibidos del servidor');
|
|
|
setTimeout(() => {
|
|
|
|
|
console.log("Datos recibidos del servidor");
|
|
|
|
|
}, 2000);
|
|
}, 2000);
|
|
|
console.log("Petición enviada");
|
|
|
|
|
|
console.log('Petición enviada');
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
> El flujo **no se detiene** esperando la respuesta del servidor.
|
|
> El flujo principal no se bloquea; la operación asíncrona se gestiona mediante el **Event Loop**.
|
|
|
> Se ejecuta el `setTimeout` de forma asíncrona.
|
|
|
|
|
|
|
|
|
|
---
|
|
---
|
|
|
|
|
|
|
|
## 2. Qué es una API REST
|
|
## 2. Qué es una API REST
|
|
|
|
|
|
|
|
Una **API REST** (Representational State Transfer) permite **comunicarse entre aplicaciones a través de HTTP**.
|
|
Una **API REST (Representational State Transfer)** permite la comunicación entre aplicaciones a través de **HTTP**. En una aplicación sanitaria, una API REST expone recursos como **personal, material, activaciones o inventario**.
|
|
|
Tu app de gestión sanitaria puede usar una API REST para **consultar pacientes, recursos o personal** almacenados en un servidor remoto.
|
|
|
|
|
|
|
|
|
|
Las API REST suelen **intercambiar datos en formato JSON**.
|
|
Las API REST intercambian datos en **formato JSON**, fácilmente mapeables a **interfaces TypeScript**.
|
|
|
|
|
|
|
|
---
|
|
---
|
|
|
|
|
|
|
|
## 3. Métodos HTTP más comunes
|
|
## 3. Métodos HTTP más comunes
|
|
|
|
|
|
|
|
| Método | Acción | Ejemplo en la app |
|
|
| Método | Acción | Ejemplo en la aplicación |
|
|
|
| ---------- | ------------------------------- | --------------------------------------- |
|
|
| ------ | ------------------- | ------------------------ |
|
|
|
| **GET** | Obtener información | Consultar la lista de recursos |
|
|
| GET | Obtener información | Consultar recursos |
|
|
|
| **POST** | Crear nuevo elemento | Añadir un nuevo paciente |
|
|
| POST | Crear un recurso | Añadir personal |
|
|
|
| **PUT** | Actualizar un registro completo | Editar los datos de un recurso |
|
|
| PUT | Actualizar completo | Editar recurso |
|
|
|
| **PATCH** | Actualizar parcialmente | Cambiar solo la cantidad de mascarillas |
|
|
| PATCH | Actualizar parcial | Cambiar estado |
|
|
|
| **DELETE** | Eliminar un registro | Eliminar un paciente dado de alta |
|
|
| DELETE | Eliminar | Dar de baja |
|
|
|
|
|
|
|
|
---
|
|
---
|
|
|
|
|
|
|
|
## 4. Qué es JSON
|
|
## 4. Qué es JSON
|
|
|
|
|
|
|
|
**JSON (JavaScript Object Notation)** es el formato estándar de intercambio de datos entre cliente y servidor.
|
|
JSON es el formato estándar de intercambio de datos. En TypeScript, el JSON recibido se transforma en **objetos tipados**.
|
|
|
|
|
|
|
|
Se basa en **pares clave–valor**, muy similar a los objetos literales de JavaScript.
|
|
```ts
|
|
|
|
interface RecursoDTO {
|
|
|
|
id: string;
|
|
|
|
unidad: string;
|
|
|
|
tipoRecurso: 'HUMANO' | 'MATERIAL';
|
|
|
|
}
|
|
|
|
|
|
|
|
```javascript
|
|
const recurso: RecursoDTO = {
|
|
|
// Objeto literal
|
|
id: '1hd72h3kw8fhs7dh34',
|
|
|
const recurso = {
|
|
unidad: 'JMAPER',
|
|
|
id: "1hd72h3kw8fhs7dh34",
|
|
tipoRecurso: 'HUMANO',
|
|
|
unidad: "JMAPER",
|
|
|
|
|
tipoRecurso: "HUMANO"
|
|
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
// Conversión a JSON
|
|
const json: string = JSON.stringify(recurso);
|
|
|
const json = JSON.stringify(recurso);
|
|
const obj: RecursoDTO = JSON.parse(json);
|
|
|
console.log(json); // '{"id":"1hd72h3kw8fhs7dh34","unidad":"JMAPER","tipoRecurso":"HUMANO"}'
|
|
|
|
|
|
|
|
|
|
// Convertir JSON a objeto
|
|
|
|
|
const obj = JSON.parse(json);
|
|
|
|
|
console.log(obj.id); //"1hd72h3kw8fhs7dh34"
|
|
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
---
|
|
---
|
|
|
|
|
|
|
|
## 5. Peticiones HTTP con `fetch()`
|
|
## 5. Peticiones HTTP con `fetch`
|
|
|
|
|
|
|
|
`fetch` devuelve una `Promise<Response>`. En TypeScript se tipa explícitamente la respuesta esperada.
|
|
|
|
|
|
|
|
La función **`fetch()`** permite realizar peticiones HTTP de forma asíncrona.
|
|
```ts
|
|
|
Devuelve una **Promise**, lo que significa que podemos usar `.then()` o `async/await`.
|
|
async function obtenerRecursos(): Promise<RecursoDTO[]> {
|
|
|
|
const respuesta = await fetch('https://api.panacea.com/recursos');
|
|
|
|
|
|
|
|
### Ejemplo: obtener recursos del servidor
|
|
if (!respuesta.ok) {
|
|
|
|
throw new Error(`Error HTTP: ${respuesta.status}`);
|
|
|
|
}
|
|
|
|
|
|
|
|
return respuesta.json();
|
|
|
|
}
|
|
|
|
|
|
|
|
```javascript
|
|
obtenerRecursos()
|
|
|
fetch("https://api.panacea.com/recursos")
|
|
.then((recursos: RecursoDTO[]) => {
|
|
|
.then(res => res.json())
|
|
console.log('Recursos disponibles:', recursos);
|
|
|
.then(data => {
|
|
|
|
|
console.log("Recursos disponibles:", data);
|
|
|
|
|
})
|
|
})
|
|
|
.catch(error => {
|
|
.catch((error: Error) => {
|
|
|
console.error("Error al obtener los datos:", error);
|
|
console.error('Error al obtener los datos:', error.message);
|
|
|
});
|
|
});
|
|
|
```
|
|
```
|
|
|
|
|
|
| ... | @@ -91,79 +93,74 @@ fetch("https://api.panacea.com/recursos") |
... | @@ -91,79 +93,74 @@ fetch("https://api.panacea.com/recursos") |
|
|
|
|
|
|
|
## 6. Uso de `async` y `await`
|
|
## 6. Uso de `async` y `await`
|
|
|
|
|
|
|
|
La sintaxis `async/await` simplifica el manejo de promesas, haciéndolo más legible y cercano al código secuencial.
|
|
`async/await` permite escribir código asíncrono con estructura secuencial, manteniendo el tipado.
|
|
|
|
|
|
|
|
```javascript
|
|
```ts
|
|
|
async function cargarRecursos() {
|
|
async function cargarRecursos(): Promise<void> {
|
|
|
try {
|
|
try {
|
|
|
const respuesta = await fetch("https://api.panacea.com/recursos");
|
|
const respuesta = await fetch('https://api.panacea.com/recursos');
|
|
|
const datos = await respuesta.json();
|
|
const datos: RecursoDTO[] = await respuesta.json();
|
|
|
console.table(datos);
|
|
console.table(datos);
|
|
|
} catch (error) {
|
|
} catch (error) {
|
|
|
console.error("Error al cargar recursos:", error);
|
|
console.error('Error al cargar recursos', error);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
cargarRecursos();
|
|
cargarRecursos();
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
> `await` detiene la ejecución dentro de la función **asíncrona** hasta que se resuelve la promesa.
|
|
|
|
|
|
|
|
|
|
---
|
|
---
|
|
|
|
|
|
|
|
## 7. Envío de datos (POST)
|
|
## 7. Envío de datos (POST)
|
|
|
|
|
|
|
|
```javascript
|
|
```ts
|
|
|
async function agregarRecurso() {
|
|
async function agregarRecurso(nuevo: RecursoDTO): Promise<RecursoDTO> {
|
|
|
const nuevo = {
|
|
const respuesta = await fetch('https://api.panacea.com/recursos', {
|
|
|
id: "17d263hf84j6sh3h9sjd",
|
|
method: 'POST',
|
|
|
unidad: "JMAPER",
|
|
headers: { 'Content-Type': 'application/json' },
|
|
|
tipoRecurso: "HUMANO"
|
|
body: JSON.stringify(nuevo),
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const respuesta = await fetch("https://api.panacea.com/recursos", {
|
|
|
|
|
method: "POST",
|
|
|
|
|
headers: { "Content-Type": "application/json" },
|
|
|
|
|
body: JSON.stringify(nuevo)
|
|
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
const resultado = await respuesta.json();
|
|
if (!respuesta.ok) {
|
|
|
console.log("Recurso agregado:", resultado);
|
|
throw new Error('Error al crear el recurso');
|
|
|
|
}
|
|
|
|
|
|
|
|
return respuesta.json();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
agregarRecurso();
|
|
agregarRecurso({
|
|
|
|
id: '17d263hf84j6sh3h9sjd',
|
|
|
|
unidad: 'JMAPER',
|
|
|
|
tipoRecurso: 'HUMANO',
|
|
|
|
}).then((resultado: RecursoDTO) => {
|
|
|
|
console.log('Recurso agregado:', resultado);
|
|
|
|
});
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
---
|
|
---
|
|
|
|
|
|
|
|
## 8. Actualización y eliminación
|
|
## 8. Actualización y eliminación
|
|
|
|
|
|
|
|
### PATCH (actualizar registro completo)
|
|
### PATCH (actualización parcial)
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
|
async function actualizarRecurso(id) {
|
|
|
|
|
const recursoActualizado = {
|
|
|
|
|
id: "17d263hf84j6sh3h9sjd",
|
|
|
|
|
unidad: "JMAPER",
|
|
|
|
|
tipoRecurso: "MATERIAL"
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
```ts
|
|
|
|
async function actualizarRecurso(id: string, datos: Partial<RecursoDTO>): Promise<void> {
|
|
|
await fetch(`https://api.panacea.com/recursos/${id}`, {
|
|
await fetch(`https://api.panacea.com/recursos/${id}`, {
|
|
|
method: "PATCH",
|
|
method: 'PATCH',
|
|
|
headers: { "Content-Type": "application/json" },
|
|
headers: { 'Content-Type': 'application/json' },
|
|
|
body: JSON.stringify(recursoActualizado)
|
|
body: JSON.stringify(datos),
|
|
|
});
|
|
});
|
|
|
}
|
|
}
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
### DELETE (eliminar registro)
|
|
### DELETE (eliminación)
|
|
|
|
|
|
|
|
```javascript
|
|
```ts
|
|
|
async function eliminarRecurso(id) {
|
|
async function eliminarRecurso(id: string): Promise<void> {
|
|
|
await fetch(`https://api.panacea.com/recursos/${id}`, {
|
|
await fetch(`https://api.panacea.com/recursos/${id}`, {
|
|
|
method: "DELETE"
|
|
method: 'DELETE',
|
|
|
});
|
|
});
|
|
|
console.log("Recurso eliminado correctamente");
|
|
console.log('Recurso eliminado correctamente');
|
|
|
}
|
|
}
|
|
|
```
|
|
```
|
|
|
|
|
|
| ... | @@ -171,16 +168,13 @@ async function eliminarRecurso(id) { |
... | @@ -171,16 +168,13 @@ async function eliminarRecurso(id) { |
|
|
|
|
|
|
|
## 9. CORS (Cross-Origin Resource Sharing)
|
|
## 9. CORS (Cross-Origin Resource Sharing)
|
|
|
|
|
|
|
|
**CORS** es un mecanismo de seguridad que controla qué dominios pueden acceder a los recursos de una API.
|
|
CORS es un mecanismo de seguridad del navegador. El servidor debe permitir explícitamente el origen del frontend.
|
|
|
Si tu frontend (`http://localhost:3000`) intenta acceder a una API en otro dominio (`https://api.panacea.com`), el servidor debe **permitir explícitamente** esa conexión.
|
|
|
|
|
|
|
|
|
|
Ejemplo de encabezado que el servidor debe devolver:
|
|
|
|
|
|
|
|
|
|
```
|
|
```
|
|
|
Access-Control-Allow-Origin: http://localhost:3000
|
|
Access-Control-Allow-Origin: http://localhost:4200
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
> Si CORS no está configurado, el navegador **bloqueará la petición por seguridad**.
|
|
> En Angular, este aspecto se gestiona principalmente en el **backend** o mediante **proxies de desarrollo**.
|
|
|
|
|
|
|
|
---
|
|
---
|
|
|
|
|
|
| ... | @@ -192,36 +186,31 @@ Access-Control-Allow-Origin: http://localhost:3000 |
... | @@ -192,36 +186,31 @@ Access-Control-Allow-Origin: http://localhost:3000 |
|
|
<button id="btnCargar">Cargar recursos</button>
|
|
<button id="btnCargar">Cargar recursos</button>
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
```javascript
|
|
```ts
|
|
|
const btn = document.getElementById("btnCargar");
|
|
const btn = document.getElementById('btnCargar') as HTMLButtonElement;
|
|
|
const lista = document.getElementById("lista");
|
|
const lista = document.getElementById('lista') as HTMLUListElement;
|
|
|
|
|
|
|
|
btn.addEventListener("click", async () => {
|
|
btn.addEventListener('click', async (): Promise<void> => {
|
|
|
try {
|
|
try {
|
|
|
const res = await fetch("https://api.panacea.com/recursos");
|
|
const recursos = await obtenerRecursos();
|
|
|
const recursos = await res.json();
|
|
lista.innerHTML = '';
|
|
|
lista.innerHTML = ""; // limpiar lista previa
|
|
|
|
|
|
|
|
|
|
recursos.forEach(r => {
|
|
recursos.forEach((r: RecursoDTO) => {
|
|
|
const li = document.createElement("li");
|
|
const li = document.createElement('li');
|
|
|
li.textContent = `${r.tipo}: ${r.cantidad}`;
|
|
li.textContent = `${r.tipoRecurso}: ${r.unidad}`;
|
|
|
lista.appendChild(li);
|
|
lista.appendChild(li);
|
|
|
});
|
|
});
|
|
|
} catch (error) {
|
|
} catch {
|
|
|
alert("Error al cargar recursos");
|
|
alert('Error al cargar recursos');
|
|
|
}
|
|
}
|
|
|
});
|
|
});
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
> Este ejemplo conecta la **interfaz del DOM** con la **API REST**, mostrando datos dinámicamente desde el servidor.
|
|
|
|
|
|
|
|
|
|
---
|
|
---
|
|
|
|
|
|
|
|
## 11. Buenas prácticas
|
|
## 11. Buenas prácticas en TypeScript
|
|
|
|
|
|
|
|
- Usa `async/await` para código más legible.
|
|
|
|
|
- Maneja siempre los **errores** con `try...catch` al hacer peticiones a servicios externos.
|
|
|
|
|
- No compartas claves o URLs sensibles en el frontend.
|
|
|
|
|
- Define funciones reutilizables para las peticiones.
|
|
|
|
|
- Asegúrate de que tu servidor tenga configurado **CORS** correctamente.
|
|
|
|
|
|
|
|
|
|
|
* Centralizar las llamadas HTTP en **servicios**.
|
|
|
|
* Tipar siempre los datos entrantes y salientes.
|
|
|
|
* Usar `Partial<T>` para actualizaciones.
|
|
|
|
* Manejar errores HTTP explícitamente. |