Skip to main content

Datos y persistencia

Motor: PostgreSQL gestionado (DigitalOcean), DBENGINE=postgres. Venía de MySQL / SQL Server y la migración es parcial.

Entidades

Viven en proteus-backend/src/schemas/** (más src/modules/**/entities), agrupadas por schema de Postgres.

Ojo con el nombre

Acá las entidades son los schemas. Los archivos *.entity.ts solo le importan al ormconfig.json, que no se usa.

SchemaQué guardaEjemplos
customerEl paciente y su vínculo con financiadoresPatient, PatientAgreement, PatientAgreementElementCategory (cartilla), PatientInformation, ContactNumber
financesEpisodios, autorizaciones, insumos, entregasAtentionProcess, Authorization, AuthorizationDetails, AuthorizationRequest, AmbulatoryAuthorization, Billing, HospitalBedOccupancy, PatientDiagnostics, Document, Supply*, DeliveryNote*, Warehouse*
configurationCatálogos y configuración — el schema más grande (~50 entidades)Insurance, Agreement, Element, ElementCategory, Price, Product, User, Role, Permission, RolePermission, Company, EntityStatus, Diagnostic, Hospital*, DocumentType, SiiCredential
pami_billingsEl modelo de pago capitado y la facturación a PAMICapita* (Modulo, Categoria, Adenda, TasaUsoMinima, SnapshotDiario, Alerta), internacion, ambulatorio, practicasRealizadas*, liquidaciones, transmisiones, transmisionArchivo*, debitos, debitosSII, bocasAtencion
logsOMEs y logsOmePami, OmeDetalle
callcenterContactos (legacy)

Bases comunes: BasicEntity, LoggeableEntity, ContactableEntity, EntityCode, ReportEntity.

Las entidades del motor de reglas están aparte, en su módulo.

Conexiones (src/config/bbdd/)

ConexiónEstado
PostgreSQLConnectionLa conexión real y la única que se inicializa. synchronize está comentado (OFF)
MSSQLConnection⚠️ Su initialize() no se llama en ningún lado
RedisConnectionLocks distribuidos, caches y metadata de usuario
QueueConnectionRabbitMQ — ver Workers y colas
MSSQLConnection.dataSource es undefined en runtime, siempre

Tanto en modo API como en workers. Cualquier service que lea de ahí muere con TypeError: Cannot read properties of undefined (reading 'query').

Si tocás un service que la usa, apuntalo a PostgreSQLConnection.dataSource (es la misma base, las mismas variables DB_SQL_DEFAULT_*).

Consumidores que todavía arrastran el bug: DashboardKpisService, PamiBillingSIIService, ControlAccountService, PamiSIIServiceV4, bulkInsertIgnore.

Schema y "migraciones"

src/migrations/ no son migraciones de TypeORM: son scripts de seed/bootstrap idempotentes que corren al arrancar la app (RoleMigrations, RolesPermissionMigrations, EntityStatusMigrations, MovementTypeMigrations, InsuranceMigrations, AppConfigurationMCache, RulesEngineMigrations…).

El DDL real se aplica a mano: ver DDL y ops.

Los nueve gotchas de Postgres

Los que más tiempo cuestan, en orden de frecuencia:

1. synchronize está OFF

Agregar una columna a la entidad no la crea en la base. Sin el ALTER TABLE previo, toda query que toque esa entidad revienta — no solo la que usa la columna nueva.

2. operator does not exist: bigint = character varying

Falta un cast. Postgres no castea implícito. Verificá los tipos reales en information_schema.columns.

3. LIKE es case-sensitive

Usá ILIKE / ILike.

4. Columnas boolean en la entidad son smallint en la base

Comparar con COALESCE(col,0)=1 y no mandar booleanos (tira 22P02). Casos conocidos: ACTIVE, EXTERNALGENERATED, CHRONIC, AtentionProcess.BILLED.

5. bigint y numeric vuelven como string

Es el driver pg. No hagas aritmética directa sobre esos valores.

6. NULLS FIRST / NULLS LAST hay que ponerlo explícito

El default de Postgres difiere del de MySQL.

7. Baja lógica, no DELETE

ACTIVE = false + ENDDATE = now(). No borres filas.

8. El SQL crudo se arregla incrementalmente

Endpoint por endpoint, a medida que aparece roto. Mucho de lo que queda es código muerto y no vale portarlo. La receta está en Porteo de SQL.

9. Un módulo entero "que no anda y no dice por qué" puede ser la conexión

El diagnóstico más engañoso del repo

MSSQLConnection.dataSource es undefined, así que el TypeError termina en CustomResponse.error(), que lo guarda en err — y un Error serializa a {} en JSON. El front recibe {"success":false,"err":{}} sin ninguna pista.

El mensaje real está en la consola del servidor. Miralo antes de sospechar de la query.

Índices únicos y NULL

En Postgres, dos NULL se consideran distintos en un índice único. O sea: UNIQUE (COMPANYID, CODE) no impide dos filas con COMPANYID IS NULL y el mismo CODE.

Si la columna del scope es nullable, indexá COALESCE(COMPANYID, 0). Y para que la unicidad sea case-insensitive y a prueba de espacios, indexá UPPER(TRIM(CODE))upper() y btrim() son IMMUTABLE, así que se pueden indexar.

La validación de aplicación tiene que espejar la expresión del índice EXACTAMENTE

Si el índice normaliza con UPPER(TRIM(...)) y el service compara el valor crudo, el usuario ve un error 23505 de Postgres en vez del mensaje de negocio. Y validá también en el update, no solo en el create.