Catalizadora · TN GlobalLink

La tabla de contactos que TNI va a crear

Lista cerrada de campos para dbo.ClientesContactos: cuántos son, cómo se llama cada uno y de qué tipo es. Esto es lo que TNI pidió en su punto B1 para poder crear la tabla sin tener que ajustarla después.

10 de septiembre de 2026 · para el equipo de base de datos de Truly Nolen International · responde al documento «Integración TNI ↔ GlobalLink» del 8-sep

1 Por qué hace falta una tabla y no tres columnas

dbo.Clientes tiene tres espacios fijos de contacto —Ventas, Cobros y Administración—. Un cliente con más de tres personas no cabe. No es una hipótesis: así están hoy los prospectos que ya tenemos cargados.

10personas en el prospecto más grande. Siete se perderían.
8en el segundo.
4en el tercero.
3espacios que ofrece Clientes hoy.

Por eso la decisión de TNI —crear la tabla en vez de recortar a tres— es la correcta. dbo.Contactos no sirve para esto: medida su estructura, es la bitácora de visitas (FechaContacto, IdMotivoContacto, IdTipoReclamo), no un directorio de personas.

2 Una regla que no se puede romper

nvarchar, nunca varchar

Todas las columnas de texto tienen que ser nvarchar. Las cuatro bases usan el collate Modern_Spanish_CI_AS, que en una columna varchar trabaja sobre la página de códigos 1252 — y ahí no existe el cirílico: cada letra rusa se guardaría como ?, sin error y sin aviso.

Medido el 10-sep en TrulyRuso: 6.976 de 13.935 clientes tienen el nombre en cirílico y 13.712 tienen la dirección en cirílico. Hoy eso funciona porque las 52 columnas de texto de Clientes ya son nvarchar. La tabla nueva tiene que nacer igual.

la excepción, a propósito

idempotency_key sí va varchar(80), igual que en las otras cuatro tablas: su contenido lo generamos nosotros y es siempre ASCII. Mantener el mismo tipo evita que el índice tenga que convertir.

3 Los 20 campos

Los tamaños salen de dos lados: los que TNI ya usa para lo mismo en dbo.Clientes, y el largo real de lo que hoy tenemos cargado.

CampoTipoNuloPara qué
Llaves
1IdClienteContactoint IDENTITY(1,1)NOT NULLClave primaria. Mismo estilo que IdCliente e IdContacto.
2IdClienteintNOT NULLA qué cliente pertenece. FK a dbo.Clientes, cuya PK es IdCliente sola (verificado).
3IdOficinaintNOT NULLPara que los filtros por oficina funcionen sin unir con Clientes, igual que en dbo.Contactos.
4IdContactoGlobalLinkchar(36)NOT NULLNuestro identificador de la persona (UUID). Es lo que permite volver a mandar sin duplicar, y rastrear una fila hasta su origen.
La persona
5Nombrenvarchar(150)NOT NULLMismo tamaño que Clientes.ContactoVentas. Hoy el más largo que tenemos mide 36.
6Cargonvarchar(150)NULL«Gerente de Operaciones», «Dueño». El más largo que tenemos mide 86.
7EsPrincipalbitNOT NULL · def. 0Cuál de las N personas es la que además copiamos a Clientes.ContactoAdmin*. Sin esto, la tabla y el cliente pueden contradecirse.
Cómo se le habla
8Emailnvarchar(320)NULL320 es el máximo que permite el estándar de correo. Ojo: Clientes.ContactoVentasEmail es nvarchar(50) y se queda corto para muchos correos reales; acá conviene no repetirlo.
9Telefononvarchar(50)NULLFijo, en formato internacional (+502…).
10Movilnvarchar(50)NULLCelular. Puede ser distinto del fijo.
11WhatsAppnvarchar(50)NULLA veces la persona atiende WhatsApp en un número que no es su celular.
12MovilTieneWhatsAppbitNULLNulo a propósito: «no sabemos» tiene que poder distinguirse de «no tiene».
De dónde salió
13LinkedInUrlnvarchar(400)NULLPerfil de la persona. La más larga que tenemos mide 114.
14XUrlnvarchar(400)NULLPerfil en X / Twitter.
15InstagramUrlnvarchar(400)NULLPerfil en Instagram.
16FuenteUrlnvarchar(1000)NULLLa página donde se encontró a la persona. Es lo que permite auditar un dato que nadie tecleó.
Rastro
17Origennvarchar(40)NOT NULL · def. 'GlobalLink'Para que la tabla pueda sostener mañana contactos que no vengan de GlobalLink sin tener que cambiarla.
18FechaAltadatetimeNOT NULL · def. GETDATE()Cuándo se creó la fila.
19FechaModificaciondatetimeNULLQueda para cuando el addendum permita corregir una persona. Con INSERT-only se queda nula.
20idempotency_keyvarchar(80)NULLIgual que en las otras cuatro tablas, con el mismo índice único filtrado.

4 El script, listo para correr

Se corre primero en TrulyGestion. En las tres bases de producción, cuando el addendum y la autorización interna lo permitan.

-- dbo.ClientesContactos · directorio completo de personas por cliente
-- TN GlobalLink ↔ TNI · 10-sep-2026 · responde al punto B1

CREATE TABLE dbo.ClientesContactos (
    IdClienteContacto     int IDENTITY(1,1)  NOT NULL,
    IdCliente             int              NOT NULL,
    IdOficina             int              NOT NULL,
    IdContactoGlobalLink  char(36)          NOT NULL,
    Nombre                nvarchar(150)    NOT NULL,
    Cargo                 nvarchar(150)    NULL,
    EsPrincipal           bit              NOT NULL
        CONSTRAINT DF_ClientesContactos_EsPrincipal DEFAULT (0),
    Email                 nvarchar(320)    NULL,
    Telefono              nvarchar(50)     NULL,
    Movil                 nvarchar(50)     NULL,
    WhatsApp              nvarchar(50)     NULL,
    MovilTieneWhatsApp    bit              NULL,
    LinkedInUrl           nvarchar(400)    NULL,
    XUrl                  nvarchar(400)    NULL,
    InstagramUrl          nvarchar(400)    NULL,
    FuenteUrl             nvarchar(1000)   NULL,
    Origen                nvarchar(40)     NOT NULL
        CONSTRAINT DF_ClientesContactos_Origen DEFAULT (N'GlobalLink'),
    FechaAlta             datetime         NOT NULL
        CONSTRAINT DF_ClientesContactos_FechaAlta DEFAULT (GETDATE()),
    FechaModificacion     datetime         NULL,
    idempotency_key       varchar(80)      NULL,
    CONSTRAINT PK_ClientesContactos PRIMARY KEY CLUSTERED (IdClienteContacto),
    CONSTRAINT FK_ClientesContactos_Clientes FOREIGN KEY (IdCliente)
        REFERENCES dbo.Clientes (IdCliente)
);
GO

-- La misma persona no entra dos veces, aunque reintentemos el envío.
CREATE UNIQUE INDEX UX_ClientesContactos_GlobalLink
    ON dbo.ClientesContactos (IdContactoGlobalLink);
GO

-- Filtrado: SQL Server admite un solo NULL en un índice único, y acá van muchos.
CREATE UNIQUE INDEX UX_ClientesContactos_idempotency
    ON dbo.ClientesContactos (idempotency_key)
    WHERE idempotency_key IS NOT NULL;
GO

-- La consulta natural: «dame las personas de este cliente».
CREATE INDEX IX_ClientesContactos_Cliente
    ON dbo.ClientesContactos (IdCliente) INCLUDE (Nombre, EsPrincipal);
GO

-- Mismo modelo de permisos que las otras cuatro tablas del addendum.
GRANT SELECT, INSERT ON dbo.ClientesContactos TO user_globallink_writer;
DENY UPDATE, DELETE ON dbo.ClientesContactos TO user_globallink_writer;
GO

5 Cómo la vamos a llenar

Al crear el prospecto

Una fila por persona conocida. La que marcamos EsPrincipal = 1 es la misma que copiamos a Clientes.ContactoAdmin, …AdminEmail y …AdminTelefono, para que las dos versiones no se contradigan.

Después

Si aparece una persona nueva, entra como fila nueva. Nunca corregimos ni borramos una existente: el addendum prohíbe UPDATE y DELETE, y esta tabla se crea con la misma prohibición.