Skip to content

Modelo relacional

Hernan Lopez edited this page Oct 7, 2026 · 1 revision

Modelo relacional de persistencia EF Core

Esta referencia describe el modelo opcional agregado por NetArcaWs.EntityFrameworkCore y los scripts SQL generados desde ese modelo. No representa una conversión automática del diario SQLite integrado en el paquete principal. Hay dos artefactos distintos: 15 scripts DDL actuales (tres selecciones por cada uno de cinco proveedores) y 10 scripts SQL de las migraciones versionadas 0→latest (un módulo por proveedor). El primer grupo describe el modelo presente; el segundo contiene las operaciones de migración realmente versionadas. Ninguno se usa para adoptar automáticamente tablas existentes.

Selección de módulos

La selección inmutable de NetArcaWsModelOptions decide qué entidades entran en el modelo EF. La selección de servicios no agrega tablas: habilita los servicios admitidos para las filas del diario o los tickets compartidos.

Selección Tablas incluidas
Sin módulos 0
AddInvoicing(...) 3: NetArcaInvoices, NetArcaInvoiceRevisions, NetArcaInvoiceSeriesReservations
AddWsaaTickets(...) 1: NetArcaWsaaTickets
Ambos módulos 4

El diario admite WSFEv1, WSFEXv1 y WSMTXCA. Los tickets permiten cualquier servicio autenticado admitido por ArcaService, salvo WSAA. La aplicación debe usar la misma selección al configurar el modelo y registrar sus stores.

NetArcaWsModelOptions model = NetArcaWsModelOptions.Configure(options =>
    options.AddInvoicing(ArcaService.Wsfev1, ArcaService.Wsfexv1)
        .AddWsaaTickets(ArcaService.Wsfev1));

modelBuilder.AddNetArcaWs(model);

Diagrama lógico

Las líneas discontinuas muestran la relación que el código interpreta entre hashes. El modelo EF actual define cero claves foráneas: estas líneas no son restricciones físicas y no hay acciones ON DELETE, cascadas ni restricciones CHECK para enums.

flowchart LR
  I[NetArcaInvoices<br/>PK TenantHash + KeyHash]
  R[NetArcaInvoiceRevisions<br/>PK TenantHash + KeyHash + RevisionNumber]
  S[NetArcaInvoiceSeriesReservations<br/>PK SeriesHash]
  T[NetArcaWsaaTickets<br/>PK KeyHash]
  I -. "historial por TenantHash + KeyHash; sin FK" .-> R
  I -. "reserva activa por TenantHash + KeyHash; sin FK" .-> S
Loading

La tabla WSAA es independiente de las entidades fiscales. La identidad lógica de un ticket se resume en KeyHash, derivado de certificado, endpoint y servicio. No incluye tenant ni CUIT: la autorización del contexto corresponde a la aplicación y no se almacena en esta identidad remota. Una rotación de certificado puede crear otra identidad para ARCA y aún así colisionar con alias remotos.

Claves e índices

Tabla Claves e índices del modelo
NetArcaInvoices PK (TenantHash, KeyHash) para la clave idempotente dentro del tenant; único FiscalHash para (ambiente, CUIT, punto de venta, tipo, número) global a tenants, servicios y protocolos; único RemoteHash cuando existe un ID remoto; índice (TenantHash, CreatedUtcTicks) para listar por tenant/fecha.
NetArcaInvoiceRevisions PK (TenantHash, KeyHash, RevisionNumber); conserva las versiones previas rechazadas para auditoría.
NetArcaInvoiceSeriesReservations PK SeriesHash para reservar (ambiente, CUIT, punto de venta, tipo) mientras el resultado no sea terminal; único (TenantHash, KeyHash) para vincular lógicamente una operación.
NetArcaWsaaTickets PK KeyHash; índice (State, UpdatedUtcTicks) para localizar coordinación pendiente y tickets.

Las identidades textuales se hashean con SHA-256 sobre valores codificados de forma determinista; los hashes almacenados son hexadecimales de 64 caracteres. TenantHash y KeyHash protegen comparaciones ordinales frente a collations, pero los identificadores legibles originales también se conservan para validar integridad y resolver colisiones. La clave idempotente no define la unicidad fiscal global. FiscalHash excluye el tenant, el servicio y el protocolo para que una CUIT no pueda duplicar la misma identidad fiscal bajo otra partición.

RemoteHash se calcula con ambiente, CUIT, servicio e ID remoto. El índice único SQL Server generado es filtrado con WHERE RemoteHash IS NOT NULL; SQLite, MySQL, MariaDB y PostgreSQL generan un índice único simple y sus motores permiten múltiples valores NULL distintos. En todos los casos solo se reserva un ID remoto presente.

Diccionario: NetArcaInvoices

Todos los campos salvo RemoteHash, RemoteRequestId, LeaseUntilMilliseconds, AuthorizationCode y ResponseXml son NOT NULL.

Columna Longitud EF Significado
TenantHash 64 SHA-256 del identificador de tenant; parte de la PK.
KeyHash 64 SHA-256 de la clave idempotente; parte de la PK junto al tenant.
TenantId 128 Identificador de tenant original para comprobación de integridad/colisión.
IdempotencyKey 128 Clave idempotente original.
Service 128 Nombre de servicio fiscal seleccionado.
ServiceHash 64 Hash de servicio usado en las comprobaciones de integridad.
Environment — ArcaEnvironment como entero: 0 homologación, 1 producción.
Cuit — CUIT representada, long.
PointOfSale — Punto de venta, int.
VoucherType — Tipo de comprobante, int.
VoucherNumber — Número de comprobante, long.
FiscalHash 64 Hash de identidad fiscal global. Índice único.
RemoteHash 64 Nullable; hash de identidad de solicitud remota cuando existe. Índice único.
RemoteRequestId — Nullable; ID de solicitud específico del servicio.
Payload — Snapshot fiscal congelado; no contiene Token/Sign WSAA.
PayloadHash 64 SHA-256 del payload congelado.
CanonicalVersion — Versión de codificación canónica del snapshot.
State — InvoiceState como entero: 0 Prepared, 1 Submitting, 2 Unknown, 3 Reconciling, 4 Authorized, 5 Rejected, 6 Conflict, 7 ManualReview.
Version — Versión de fencing del intento.
Attempt — Número de intento/claim registrado.
LeaseUntilMilliseconds — Nullable; instante de vencimiento de lease como Unix epoch en milisegundos UTC.
AuthorizationCode — Nullable; código de autorización devuelto por ARCA.
ResponseXml — Nullable; respuesta fiscal conservada para reconciliación/auditoría.
CreatedUtcTicks — DateTimeOffset.UtcTicks: ticks .NET de 100 ns desde 0001-01-01 UTC.

Diccionario: NetArcaInvoiceRevisions

Todos los campos salvo RemoteRequestId, LeaseUntilMilliseconds, AuthorizationCode y ResponseXml son NOT NULL. Cada fila representa un snapshot anterior de una revisión explícita de un rechazo.

Columna Longitud EF Significado
TenantHash 64 Hash del tenant; primera parte de la PK compuesta.
KeyHash 64 Hash idempotente; segunda parte de la PK compuesta.
RevisionNumber — Número secuencial de revisión; tercera parte de la PK.
TenantId, IdempotencyKey 128 cada una Identificadores originales conservados en el snapshot.
Service 128 Servicio fiscal.
Environment — ArcaEnvironment entero: 0 homologación, 1 producción.
Cuit — CUIT representada, long.
PointOfSale — Punto de venta, int.
VoucherType — Tipo de comprobante, int.
VoucherNumber — Número, long.
RemoteRequestId — Nullable; ID remoto que se debe preservar entre revisión y envío.
Payload — Payload fiscal histórico.
PayloadHash 64 Hash SHA-256 del payload.
SnapshotHash 64 Hash de integridad del snapshot completo de revisión.
CanonicalVersion — Versión canónica del payload.
State — Estado histórico como entero de InvoiceState; el código valida estados permitidos.
Version — Versión de operación histórica.
Attempt — Intentos registrados en la versión histórica.
LeaseUntilMilliseconds — Nullable; lease de la operación, Unix epoch ms UTC.
AuthorizationCode, ResponseXml — Nullable; datos de respuesta guardados en el snapshot.

Diccionario: NetArcaInvoiceSeriesReservations

Las tres columnas son NOT NULL. Esta tabla no tiene FK física a la factura. La aplicación libera la reserva mediante las transiciones terminales del diario.

Columna Longitud EF Significado
SeriesHash 64 SHA-256 de ambiente, CUIT, punto de venta y tipo; clave primaria global.
TenantHash 64 Hash del tenant que abrió la operación.
KeyHash 64 Hash idempotente que identifica lógicamente la factura en ese tenant.

Diccionario: NetArcaWsaaTickets

Todos los campos salvo OwnerId, LeaseUntilUtcTicks, ExpiresUtcTicks, KeyId, Nonce, Ciphertext y Tag son NOT NULL.

Columna Longitud EF Significado
KeyHash 64 SHA-256 de certificado SHA-256, endpoint y servicio; PK y clave lógica remota.
CertificateHash 64 Huella SHA-256 del certificado.
Endpoint 512 Endpoint oficial elegido por ambiente.
Service 32 Nombre de servicio solicitado a WSAA.
State — Estado entero interno: 1 Pending, 2 Unknown, 3 Ticket; no hay CHECK SQL.
OwnerId 36 Nullable; identificador de propietario del claim activo.
Fence — Token creciente de fencing para impedir que propietarios antiguos sobrescriban claims nuevos.
Version — Versión creciente para compare-and-swap del registro.
LeaseUntilUtcTicks — Nullable; vencimiento de claim en ticks .NET UTC (100 ns).
ExpiresUtcTicks — Nullable; vencimiento TA real reportado por WSAA en ticks .NET UTC (100 ns).
KeyId 128 Nullable; ID para resolver la clave externa del protector.
Nonce, Ciphertext, Tag — Nullable; componentes AES-GCM del ticket cifrado. El protector valida nonce de 12 bytes y tag de 16 bytes; el texto cifrado conserva el XML serializado.
UpdatedUtcTicks — Instante de actualización en ticks .NET UTC (100 ns).

El keyring no se guarda en la base de datos. La aplicación registra el IWsaaTicketProtector, distribuye de forma segura las mismas claves entre réplicas y conserva las claves antiguas mientras existan filas cifradas con su KeyId. Token y Sign están dentro del ciphertext; el endpoint, servicio, huella de certificado, nonce y tag quedan visibles como metadatos.

Tipos, límites y restricciones

Los scripts concretan los tipos de cada proveedor. EF configura HasMaxLength para los identificadores y hashes mostrados arriba. SQL Server, MySQL, MariaDB y PostgreSQL traducen esos límites a tipos nvarchar(n), varchar(n) o character varying(n). SQLite los emite como TEXT, que no impone el máximo de caracteres. La biblioteca valida identificadores y el tamaño de payload fiscal; HasMaxLength no equivale a una validación uniforme en la base.

La biblioteca también valida estados y el tamaño de clave/nonce/tag del ticket. El modelo no crea CHECK constraints para estados, hash-length, nonce/tag, ni FK o cascadas. Las columnas payload/XML y autorización no tienen HasMaxLength. RemoteHash es nullable; el DDL de SQL Server usa índice filtrado y los otros motores permiten múltiples NULL distintos en el índice único simple.

Las migraciones oficiales usan contextos dedicados con modelos fijos por motor y módulo; el ArcaWsDbContext operativo y los contextos del consumidor mantienen sus propias historias. Los historiales son __NetArcaWsInvoiceMigrations y __NetArcaWsTicketMigrations. En PostgreSQL están en public; en SQL Server, en dbo; MySQL, MariaDB y SQLite usan el schema por defecto del motor. EF Core SQLite puede crear además __EFMigrationsLock para coordinar la ejecución local.

La tarea de despliegue configura un extra de migraciones. GetStatusAsync informa Empty, UpgradeAvailable, Current, UntrackedSchema, UnknownAppliedMigration, InconsistentHistory o TrackedSchemaIncomplete, con IDs conocidos/aplicados/pendientes y tablas propias presentes. GenerateScript(module, fromMigration: "0", toMigration: null, idempotent: false) exige módulo seleccionado y extremos conocidos ascendentes; null significa latest. El flag idempotente depende del proveedor; SQLite lanza NotSupportedException si se solicita. Generar el script no abre conexión. ApplyAsync preflighta y aplica en orden determinista. Revisá GenerateScript cuando corresponda y ejecutá ApplyAsync. La API solo avanza y conserva tablas, historias y datos cuando se deshabilita un módulo. El actor de despliegue necesita permisos de cambio de esquema; la API de runtime debe recibir solo los permisos de datos que usa. Serializá los trabajos de despliegue por base: el preflight ocurre fuera del lock de EF y no existe coordinación con otro proceso.

Para actualizar, el consumidor actualiza el paquete y aplica las migraciones oficiales pendientes; no genera migrations add para estas tablas dedicadas. Los cambios futuros del modelo requieren una nueva migración por módulo y proveedor, sin editar las publicadas, y pruebas desde la anterior. SQLite no ofrece SQL idempotente y los scripts iniciales 0→latest no son scripts de adopción.

Tablas preexistentes sin historia, IDs desconocidos, huecos de historia o tablas incompletas se rechazan antes de aplicar. Revisá manualmente propietario y esquema al planificar una adopción futura. No se inserta history automáticamente, no se eliminan datos y no hay una conversión automática del diario SQLite del core, de EnsureCreated, DDL manual ni historias del consumidor. El almacenamiento de certificados y una cola/worker no forman parte de este modelo.

Scripts generados

dotnet run --project tools/NetArcaWs.Build -- persistence-schema genera 15 DDL actuales desde los modelos EF; --check detecta archivos ausentes, distintos o sobrantes sin reescribirlos.

dotnet run --project tools/NetArcaWs.Build -- persistence-migrations genera 10 scripts desde migraciones versionadas reales, uno por módulo y proveedor; --check valida esos artefactos. Son upgrades iniciales 0→latest, no idempotentes ni una alternativa de baseline.

Proveedor Facturación Tickets WSAA Ambos (solo DDL)
SQLite invoicing wsaa-tickets all
MySQL invoicing wsaa-tickets all
MariaDB invoicing wsaa-tickets all
PostgreSQL invoicing wsaa-tickets all
SQL Server invoicing wsaa-tickets all

Los 10 scripts de migración versionados son independientes de los DDL anteriores:

Motor Facturación Tickets WSAA
SQLite 0→latest 0→latest
MySQL 0→latest 0→latest
MariaDB 0→latest 0→latest
PostgreSQL 0→latest 0→latest
SQL Server 0→latest 0→latest

Estos archivos son operaciones de upgrade inicial reales de las cadenas versionadas; no son idempotentes ni un mecanismo de adopción/baseline. Se generan y revisan con dotnet run --project tools/NetArcaWs.Build -- persistence-migrations y se verifican con --check.

Clone this wiki locally