Desarrolladores
Known issues y notas de traspaso
Deuda técnica conocida, peculiaridades del proyecto y pendientes verificados en el código, con su recomendación, para el equipo que recibe ixiclinic.
Esta página recoge, de forma honesta y constructiva, los puntos pendientes y las peculiaridades reales del proyecto a la fecha del traspaso. Cada punto está verificado en el código; no es una lista de sospechas. El objetivo es que el equipo entrante sepa dónde están los bordes ásperos y cómo abordarlos, no asignar culpas.
Naming ixiclinic vs ledxa (heredado)
- Qué es. El producto es ixiclinic, pero el proyecto nació con el nombre corto "ledxa".
Las referencias dentro del repositorio ya se unificaron a
ixiclinic: losdocker-compose(stack, contenedores, red y defaultsDB_NAME/DB_USER), las claves delocalStoragedel admin y del lab-site, y el default deGITHUB_RELEASES_REPOenapps/api/src/config/env.ts(ahora vacío, se configura por entorno). - Por qué importa. Si se reutilizan datos o infraestructura de un entorno anterior, los
nombres de contenedor (
ixiclinic-postgres) y las rutas (/opt/ixiclinic) pueden no coincidir con los nuevos (ixiclinic-*). Es un detalle de operación, no de código. - Recomendación. Al montar el entorno nuevo, usar los nombres
ixiclinic-*que ya trae el repo y fijarGITHUB_RELEASES_REPOal repositorio de releases propio.
Naming mixto ES/EN en las rutas del admin
- Qué es. En
apps/admin/src/app/(dashboard)/conviven carpetas en español (actividades,encuentros,recetas,services) e inglés (orders,billing,inventory,patients,results,samples, etc.). - Por qué importa. La inconsistencia hace que adivinar la ruta de una pantalla sea impredecible y dificulta el onboarding.
- Recomendación. Tratarlo como una convención a respetar mientras no se unifique: los términos de dominio clínico/fiscal en español son deliberados (ver Convenciones). Si se decide normalizar, hacerlo en una pasada dedicada y con redirects, no de forma incremental.
Cobertura de tests por ampliar
- Qué es. Solo el API tiene pruebas (Vitest): esquemas/RBAC y, ahora, las de tokens JWT
(
apps/api/src/lib/jwt.test.ts). No hay tests de integración HTTP ni cobertura en las apps web y mobile. El workflowci.ymlya corretypecheck,lint,testybuilden cada PR. - Por qué importa. No hay red de seguridad para regresiones en HTTP, RBAC por ruta, rotación de tokens ni en la lógica de negocio (facturación, órdenes, multi-tenancy).
- Recomendación. Priorizar tests de integración HTTP sobre los flujos críticos (auth, aislamiento por tenant, RBAC por módulo, facturación). Ver Testing.
Política de lint y deuda de tipos
- Qué es.
pnpm lintpasa sin errores. Para no bloquear sobre código existente, la config de ESLint (eslint.config.mjs) trata como warnings dos familias:@typescript-eslint/no-explicit-anyy las reglas avanzadas del React Compiler (set-state-in-effect,refs,purity,static-components,immutability). Las reglas de correctitud real (p. ej.rules-of-hooks) siguen siendo error. - Por qué importa. Hay uso de
any(sobre todo enapps/admin) y patrones que el React Compiler marca; son avisos, no fallos, pero conviene reducirlos. - Recomendación. Bajar la deuda de forma incremental.
apps/consoleyapps/portalya quedaron libres deanyy sirven de referencia del patrón a seguir (tipar las respuestas del API con tipos locales/compartidos en vez deany).
Dos bases de datos a mantener en sync
- Qué es. Desarrollo usa Neon Serverless PostgreSQL; producción usa PostgreSQL en Docker en el VPS. Los cambios de esquema deben aplicarse a ambas.
- Por qué importa. Es fácil que las migraciones diverjan (drift): un cambio aplicado en dev y olvidado en prod —o al revés— rompe el arranque o produce errores en runtime.
- Recomendación. Aplicar siempre el mismo conjunto de migraciones a los dos entornos y revisar el flujo en Migraciones antes de generar o aplicar una.
Reportes DGII 606/607/608 sin generador
- Qué es. La tabla
dgii_reportsexiste enapps/api/src/db/schema/dgii.ts(conreport_typepara 606/607/608), pero no hay módulo ni endpoints que los generen: no existe un módulodgiienapps/api/src/modules/y la tabla no se referencia desde ningún otro punto del código. - Por qué importa. Los reportes 606/607/608 son una obligación fiscal recurrente ante la DGII; hoy la base de datos los contempla pero el producto no los produce.
- Recomendación. Implementar el módulo de reportes DGII (generación + exportación) sobre la tabla ya modelada. Ver el contexto fiscal en DGII / fiscal.
Seguridad
Durante la preparación de la entrega se aplicó un endurecimiento base de la API, verificado
con typecheck y tests:
- Algoritmo JWT fijado. Las verificaciones fijan
algorithms: ['HS256'](apps/api/src/lib/jwt.ts), como defensa frente a confusión de algoritmo. - Uploads sin SVG. Se quitó
.svgde la allowlist (apps/api/src/modules/uploads/handlers.ts): un SVG puede traer<script>y sería XSS almacenado servido desde el mismo origen. - Rate limit por ruta. Límites más estrictos en
login,register,refreshysaas/auth/login, por encima del global de 500/min. - RBAC en rutas antes abiertas.
dashboard,search,lookupy elbroadcastde notificaciones ahora pasan porrequireModule(...). - CSP. La API mantiene la CSP desactivada a propósito: responde JSON (el navegador no
lo ejecuta como documento) y la UI de Scalar en
/docsnecesita cargar sus assets. La CSP con valor real pertenece a las apps Next (admin/portal/console/landing) y queda pendiente por requerir validación en navegador.
Pendiente: rotación one-time-use de refresh tokens
- Estado.
POST /api/auth/refresh(apps/api/src/modules/auth/service.ts) hoy emite un nuevo access token pero no rota el refresh: el mismo refresh sirve hasta el logout o su expiración (30 días). Aplica a los tres flujos (staff, SaaS y cliente). - Por qué importa. Sin rotación + detección de reuso, un refresh robado es utilizable durante toda su vida.
- Diseño recomendado (entorno nuevo: aplicarlo y validar en staging antes de prod):
- Esquema. Añadir a
refresh_tokenslas columnasrevoked_at timestampyreplaced_by text(más un índice poruser_id). Generar la migración condb:generatey revisar el.sqlpara que contenga solo eseALTER— ver el riesgo de drift en Migraciones. - Rotación atómica. En
refresh():UPDATE refresh_tokens SET revoked_at = now(), replaced_by = $nuevo WHERE token = $viejo AND revoked_at IS NULL RETURNING; insertar el nuevo y devolver{ accessToken, refreshToken }. - Ventana de gracia (~60 s). Si llega un refresh ya rotado pero dentro de la ventana,
devolver su sucesor (
replaced_by) en vez de fallar. Evita desconectar al cliente admin, que dispara variosrefreshconcurrentes por carga de página. - Detección de reuso. Un refresh ya rotado fuera de la ventana indica robo → revocar
toda la familia/usuario y responder
401. - Cliente.
tryRefreshToken(apps/admin/src/lib/api-client.ts) debe persistir el nuevo refresh en la cookie (hoy solo guarda el access). Replicar en portal y console.
- Esquema. Añadir a
- Por qué no se entregó ya implementado. Es auth-crítico y no es verificable sin base de datos ni pruebas E2E de concurrencia en el entorno de preparación; enviarlo sin validar arriesgaría el acceso del equipo el primer día. Se entrega como diseño para implementar y probar en staging.