Skip to main content

Modelo de datos

MySQL/InnoDB multi-esquema, TypeORM, entidades en src/schemas/v1/<esquema>/ y src/modules/<mod>/entities/.

El catálogo completo está en el repo

Esta página es el mapa y las trampas. La referencia entidad por entidad (columnas, relaciones, propósito) vive en docs/Obsidian/old/entidades-dominio.md del workspace de Oxitesa.

Los cinco esquemas

EsquemaQué guarda
configurationCatálogos maestros e identidad: USERS, ROLE, PRODUCTS, ELEMENTS, AGREEMENTS, EntityStatus, CommonEntityDescriptions, MANAGEMENTUNITS, VEHICLE, REPORTS, NOTIFICATIONS, MOBILECONFIG
customerPATIENTS, PATIENTINFORMATION, PATIENTSAGREEMENTS, CONTACTNUMBERS
financesEl núcleo operativo: autorizaciones, remitos, hojas de ruta, almacenes, empleados, vehículos, facturación, PATIENTSUPPLIES y todo el inventario
callcenterCONTACTLOGS y el motor de encuestas
logsEVENTSLOGS, logs del bot PAMI, REQUESTLOGS, MOBILELOGS, mensajes de WhatsApp
El esquema sale del nombre de la carpeta

Cada entidad declara su database derivándolo del directorio donde vive el archivo (path.basename(path.dirname(__filename))). Mover un archivo de entidad de carpeta le cambia el esquema en runtime.

Clases base

ClaseAporta
BasicEntityPK autogenerado, sin auditoría
LoggeableEntity (sí, con doble e en el nombre del archivo)id, registerUser, updateUser, creationDate, lastModifiedDate, stateEntityStatus, y userAction (el User en contexto)
ContactableEntityLoggableEntity + contactNumbers
ReportEntityEntidades de reporte, uso reducido

Toda mutación de una LoggableEntity produce un EVENTSLOGS. Esa es la auditoría universal del sistema.

userAction se setea antes de cualquier bifurcación

Si el service lo deja sin setear antes de guardar, la generación del EventLog explota al leer this.userAction.currentSecurityLevel, y el usuario ve un genérico "No se pudo guardar el registro del evento". Ya pasó exactamente eso al agregar una rama de lógica antes de la asignación.

Casing de tablas y columnas

UPPERCASE por defecto (PATIENTS, SUPPLIES, DELIVERYNOTES, CREATIONDATE). Las excepciones muerden al escribir SQL a mano:

ExcepciónCómo se escribe
Mixed caseEntityStatus, CommonEntityDescriptions, Authorizations, AuthorizationDetails
SingularROLE, VEHICLE

Dos patrones que atraviesan todo el modelo

EntityCode — punteros polimórficos

src/schemas/EntityCode.ts define códigos numéricos que, junto a un id, identifican "a qué entidad apunta esto" sin usar una FK. Los más frecuentes:

0 COMPANY 3 PATIENT 4 SUPPLY 6 WAREHOUSE 7 USER
9 PRODUCT 16 ELEMENT 31 GLOBALENTITY 39 DELIVERYNOTE 63 REPAIRSHOP

Se usa en SUPPLIES.ATENTITYCODE/ATENTITYID, EntityStatus.ENTITYCODE, CONTACTNUMBERS, CONTACTLOGS, EVENTSLOGS y SIGNATURE.toEntityCode/toEntityId.

EntityStatus — estados por entidad

No hay columnas enum de estado: cada entidad apunta a una fila de configuration.EntityStatus, que se resuelve por CODE + ENTITYCODE. Los catálogos de códigos están en src/migrations/EntityStatusMigrations.ts (SupplyStatus, DispatchStatus, GlobalEntityStatus…).

Dónde está físicamente un suministro

La dupla ATENTITYCODE + ATENTITYID es la ubicación. No hay una FK distinta por tipo de tenedor. Cada transición:

  1. cierra la fila abierta de SUPPLYMOVEMENTHISTORY (le pone endDate),
  2. abre una nueva,
  3. actualiza SUPPLIES.ATENTITYCODE / ATENTITYID.

Eso lo hace un stored procedure, no TypeScript — ver Stock y transferencias.

Migraciones: dos cosas distintas con el mismo nombre

CarpetaQué es
src/migrations/*.tsScripts de datos one-shot en TypeScript (roles, permisos, estados, tipos de movimiento). Se corren a mano con ts-node. No son migraciones de schema de TypeORM: no existen archivos de schema migration
migrations/**/*.sql (raíz del repo)SQL crudo que se aplica directo a la base, organizado por dominio. Acá viven los stored procedures, las views, los fix/ y los test/

Trampas de TypeORM que ya costaron caro

Nunca manager.query ni save() con la relación en null

Para anular un FK de relación usá:

await repository.update(id, { relation: null });

entity.relation = null; save() no persiste el SET NULL de un @ManyToOne: falla en silencio y el vínculo queda pegado. Eso causó el bug de "fallar una tarea deja el remito no re-vinculable".

  • getRepository() sin el queryRunner escapa de la transacción del caller. Si estás dentro de una, pasásela siempre.
  • Redis se vacía entero al arrancar (salvo en dev). No guardes ahí nada que deba sobrevivir un deploy.