ixiclinicDocs
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: los docker-compose (stack, contenedores, red y defaults DB_NAME/DB_USER), las claves de localStorage del admin y del lab-site, y el default de GITHUB_RELEASES_REPO en apps/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 fijar GITHUB_RELEASES_REPO al 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 workflow ci.yml ya corre typecheck, lint, test y build en 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 lint pasa 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-any y 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 en apps/admin) y patrones que el React Compiler marca; son avisos, no fallos, pero conviene reducirlos.
  • Recomendación. Bajar la deuda de forma incremental. apps/console y apps/portal ya quedaron libres de any y sirven de referencia del patrón a seguir (tipar las respuestas del API con tipos locales/compartidos en vez de any).

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_reports existe en apps/api/src/db/schema/dgii.ts (con report_type para 606/607/608), pero no hay módulo ni endpoints que los generen: no existe un módulo dgii en apps/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ó .svg de 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, refresh y saas/auth/login, por encima del global de 500/min.
  • RBAC en rutas antes abiertas. dashboard, search, lookup y el broadcast de notificaciones ahora pasan por requireModule(...).
  • 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 /docs necesita 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):
    1. Esquema. Añadir a refresh_tokens las columnas revoked_at timestamp y replaced_by text (más un índice por user_id). Generar la migración con db:generate y revisar el .sql para que contenga solo ese ALTER — ver el riesgo de drift en Migraciones.
    2. 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 }.
    3. 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 varios refresh concurrentes por carga de página.
    4. Detección de reuso. Un refresh ya rotado fuera de la ventana indica robo → revocar toda la familia/usuario y responder 401.
    5. 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.
  • 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.

On this page