Backoffice Telemedicina
Panel administrativo del módulo de telemedicina de Seguros Constitución: usuarios, doctores y teleconsultas en una sola consola.
React 18 · TypeScript 5 (strict) · Redux Toolkit
Rol. Desarrollo frontend completo: arquitectura feature-first, sistema de diseño, integración con la API REST del cliente y despliegue en subcarpeta con proxy propio.
Estado. Prototipo funcional entregado. Login real contra la API, cinco endpoints admin integrados y build de producción publicado en la subcarpeta /constitucion/ con proxy PHP para evitar CORS.
Panel interno de cliente · sin acceso público
Teleconsulta 0084-2291
Dra. Andreína Rojas
José Ramírez
Signos vitales
7
rutas de la aplicación
5
módulos de negocio
6
endpoints REST integrados
4
slices de Redux
167
claves de texto en el diccionario
82
archivos TypeScript / TSX
Lo que había, y lo que hay ahora
Un servicio de telemedicina repartido entre endpoints, contado en dos hojas del mismo expediente.
Sin consola, todo pasa por el swagger
La operación de telemedicina de una aseguradora vive repartida entre endpoints: un listado de usuarios, otro de doctores, otro de doctores conectados, otro de consultas con media docena de filtros y otro de reseñas. Sin una consola, el equipo administrativo depende de consultar el swagger o pedirle datos al equipo de backend cada vez que quiere saber cuántas consultas están en espera o qué médicos están disponibles. Encima, la API vive en un dominio distinto y sin cabeceras CORS abiertas, y el sitio debía publicarse en una subcarpeta de un hosting compartido donde no se puede usar mod_proxy.
Siete rutas que traducen los endpoints
Un panel React + TypeScript de siete rutas que traduce esos endpoints en pantallas operativas: un dashboard que compone cuatro KPIs y un donut de estados a partir de conteos, listados paginados con filtros y búsqueda con debounce, un grid de doctores en línea con indicador verde, y un diálogo de detalle que abre la ficha completa de una teleconsulta (paciente, doctor, póliza, sala virtual y tiempos). La capa de red está centralizada en un cliente axios con interceptores que inyectan el token Bearer, la clave de aplicación y el idioma, cierran sesión ante un 401 y levantan un modal global ante un 5xx. Para el despliegue se escribió un proxy PHP propio más reglas .htaccess, de modo que el navegador solo habla con el dominio donde está publicada la app y el problema de CORS desaparece sin depender del backend.
Cinco piezas que sostienen la consola
Dashboard compuesto sin endpoint de métricas
La API no expone un endpoint de estadísticas, así que el dashboard se arma con ocho peticiones en paralelo: totales de usuarios y doctores pidiendo page_size=1 y leyendo el campo total, doctores conectados, las cinco consultas más recientes y un conteo por cada uno de los cuatro estados.
Capa de API tipada con ApiResult
Cada servicio devuelve `ApiResult<T>` — un `{ok:true,data}` o un `{ok:false,status,title,detail,fieldErrors}` — con guardas `isOk`/`isErr`. Los errores de FastAPI (422 con array loc/msg) se traducen a errores por campo listos para react-hook-form.
Interceptores que resuelven la sesión
Un único interceptor añade `Authorization: Bearer`, la cabecera de aplicación y `Accept-Language` a toda petición leyendo el store fuera de React; en respuesta, un 401 hace logout y redirige al login respetando la subcarpeta del build, y un 5xx dispara un modal global.
Filtros de consultas fieles al swagger
La barra de consultas expone todos los parámetros del endpoint: búsqueda por nombre, ámbito paciente/doctor, estado, rango de fechas, criterio de orden (fecha, cédula del doctor, cédula del usuario) y dirección, con desactivación cruzada de campos incompatibles.
Despliegue en subcarpeta con proxy propio
Build con `base=/constitucion/`, un `api-proxy.php` que reenvía `/dev/*` al API real vía cURL preservando cabeceras, y un `.htaccess` con `RewriteBase` que además resuelve el fallback de la SPA. Sin CORS y sin mod_proxy.
Cinco pantallas, una misma consola
Del login al detalle de una teleconsulta. Cada ficha del expediente es una pantalla real del panel, recreada aquí en HTML y CSS.
Identidad
Icono de la app: cuadrado redondeado de 12 px de radio con degradado azul #38bdf8 → #2563eb y, en trazo blanco, un escudo con una cruz médica dentro.
Las maquetas reproducen la interfaz real; los datos que muestran son de ejemplo.
Todo lo que ya responde
Dieciséis comportamientos verificados sobre la API real del cliente.
Con qué está construido
Núcleo
05Estado y datos
04Formularios y validación
03UI y estilos
09Build y despliegue
06Cómo viaja una petición
Cinco módulos, un cliente axios con dos interceptores y una pasarela propia. El tráfico se dibuja como pulsos, no como flechas.
Nota de arquitectura
Feature-first sobre Vite. `src/features/` agrupa cinco módulos —auth, dashboard, users, doctors, consult— y cada uno contiene sus propias carpetas `pages/`, `components/`, `hooks/`, `api/`, `schemas/` y `types/`, de modo que una funcionalidad se lee y se borra completa desde una sola carpeta. `src/shared/` concentra lo transversal: el cliente axios con interceptores de petición y respuesta, un envoltorio `apiAxios` que decide entre JSON, multipart y x-www-form-urlencoded según el payload, los manejadores que normalizan errores de FastAPI a `ApiResult`, el store de Redux Toolkit con cuatro slices (auth, theme, lang, ui) persistidos selectivamente en localStorage, nueve primitivas de interfaz al estilo shadcn construidas con CVA, siete hooks genéricos (paginación, debounce, tema, traducción, media query, auth, api) y el diccionario de textos. `src/app/` monta providers y rutas: todas las páginas se cargan con `React.lazy`, y las privadas cuelgan de un guard `RequireAuth` que envuelve al layout. El hook `usePagination` es el corazón de los listados: recibe cualquier fetcher que devuelva `ApiResult<Paginated<T>>`, gestiona página, tamaño y parámetros, descarta respuestas obsoletas con un contador de petición y vuelve a la página 1 cuando cambian los filtros. En producción el build sale a `build/` con `base=/constitucion/` y se sirve junto a `.htaccess` y `api-proxy.php`, que actúa como pasarela server-side hacia la API real.
Cinco cosas que costaron trabajo
INC-01
La API del cliente vive en otro dominio y no devuelve cabeceras CORS para el origen del panel; en local el navegador bloqueaba el preflight del login y en el hosting compartido no existe mod_proxy para reenviar las peticiones.
Resolución
Dos pasarelas simétricas. En desarrollo, un proxy en vite.config.ts que redirige `/dev/*` a la API con changeOrigin y traza cada petición y respuesta en consola. En producción, un `api-proxy.php` propio que reconstruye la URL destino, reenvía las cabeceras relevantes (autorización, clave de aplicación, tipo de contenido, idioma) y llama a la API vía cURL, enrutado desde `.htaccess`. El navegador solo habla con el dominio del panel, así que el problema desaparece sin tocar el backend.
INC-02
El primer build de producción salía en blanco y con llamadas rotas: Vite incrusta rutas absolutas en el bundle, y la app debía publicarse en la subcarpeta /constitucion/ y no en la raíz del dominio.
Resolución
Se fijó `base: '/constitucion/'` solo en modo producción, se movió la URL del API a un `.env.production` apuntando a la propia subcarpeta, el router se monta con `basename={import.meta.env.BASE_URL}` y el redirect del interceptor 401 se reescribió sobre `BASE_URL`. Se documentó además una lista de verificación previa a subir el build y los tres sitios que hay que tocar si la subcarpeta cambia.
INC-03
El dashboard necesita totales por estado de consulta, pero la API no expone ningún endpoint de métricas ni agregados.
Resolución
Se derivan del propio listado: cada conteo pide una página de un solo elemento filtrando por estado y se queda con el campo `total` de la respuesta paginada. Las ocho llamadas (totales, conectados, recientes y los cuatro estados) salen en un único `Promise.all`, y cada fallo se reporta por separado sin tumbar el resto del tablero.
INC-04
La pantalla de reseñas solo permitía consultarlas usuario por usuario pegando un UUID, porque el endpoint exige `user_id`; hacía falta una vista global para revisar la calidad del servicio.
Resolución
Se añadió un modo «ver todas» que pide el listado completo de usuarios, dispara las peticiones de reseñas en paralelo, fusiona los resultados descartando los que fallaron y los ordena en cliente por fecha. Queda aislado tras un flag para poder sustituirlo por un endpoint agregado el día que exista.
INC-05
Al teclear en la búsqueda o cambiar filtros con la red lenta, respuestas viejas llegaban después de las nuevas y repintaban la tabla con datos caducos.
Resolución
`usePagination` lleva un contador de petición en una ref: cada carga incrementa el contador y, al resolver, compara su identificador con el actual y se descarta si ya no es la última. La búsqueda además pasa por un debounce de 400 ms y cualquier cambio de parámetros devuelve la paginación a la página 1.
El proyecto, en cuatro párrafos
Backoffice web para el módulo de telemedicina de Seguros Constitución. Reúne en una sola consola la operación diaria del servicio: quiénes son los usuarios y pacientes registrados, qué doctores están conectados en este momento, qué teleconsultas están activas, en espera, finalizadas o rechazadas, y qué reseñas dejan unos sobre otros.
El proyecto es 100% frontend: consume la API REST del cliente (dconstiapi.segurosconstitucion.com, documentada en su propio swagger) y no tiene backend propio. Toda la capa de datos está tipada en TypeScript a partir de los schemas del swagger, con un envoltorio `ApiResult<T>` que obliga a tratar el error como un valor y no como una excepción suelta.
La arquitectura sigue un patrón feature-first: cada módulo (auth, dashboard, users, doctors, consult) encapsula sus páginas, componentes, hooks, tipos y llamadas al API, mientras `shared/` concentra el cliente axios con interceptores, el store de Redux Toolkit persistido, los componentes de interfaz reutilizables y el diccionario de textos.
La estética es deliberadamente tipo macOS/iOS: superficies de vidrio con `backdrop-blur`, esquinas de 22–30 px, tres niveles de elevación por sombra, azul de sistema como color de acción, tipografía SF con ajuste óptico de tracking, y modo claro y oscuro completos definidos como tokens HSL.
Backoffice Telemedicina
¿Quieres algo así para tu producto?
Una consola que convierte un swagger en operación diaria: dashboard compuesto, listados con filtros fieles al contrato, sesión resuelta en los interceptores y despliegue en subcarpeta sin tocar el backend. Si tienes una API y te falta el panel, ése es exactamente el trabajo.