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.
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.
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.
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.
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.
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.
| Campo | Tipo | Nulo | Para qué | |
|---|---|---|---|---|
| Llaves | ||||
| 1 | IdClienteContacto | int IDENTITY(1,1) | NOT NULL | Clave primaria. Mismo estilo que IdCliente e IdContacto. |
| 2 | IdCliente | int | NOT NULL | A qué cliente pertenece. FK a dbo.Clientes, cuya PK es IdCliente sola (verificado). |
| 3 | IdOficina | int | NOT NULL | Para que los filtros por oficina funcionen sin unir con Clientes, igual que en dbo.Contactos. |
| 4 | IdContactoGlobalLink | char(36) | NOT NULL | Nuestro identificador de la persona (UUID). Es lo que permite volver a mandar sin duplicar, y rastrear una fila hasta su origen. |
| La persona | ||||
| 5 | Nombre | nvarchar(150) | NOT NULL | Mismo tamaño que Clientes.ContactoVentas. Hoy el más largo que tenemos mide 36. |
| 6 | Cargo | nvarchar(150) | NULL | «Gerente de Operaciones», «Dueño». El más largo que tenemos mide 86. |
| 7 | EsPrincipal | bit | NOT NULL · def. 0 | Cuá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 | ||||
| 8 | nvarchar(320) | NULL | 320 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. | |
| 9 | Telefono | nvarchar(50) | NULL | Fijo, en formato internacional (+502…). |
| 10 | Movil | nvarchar(50) | NULL | Celular. Puede ser distinto del fijo. |
| 11 | nvarchar(50) | NULL | A veces la persona atiende WhatsApp en un número que no es su celular. | |
| 12 | MovilTieneWhatsApp | bit | NULL | Nulo a propósito: «no sabemos» tiene que poder distinguirse de «no tiene». |
| De dónde salió | ||||
| 13 | LinkedInUrl | nvarchar(400) | NULL | Perfil de la persona. La más larga que tenemos mide 114. |
| 14 | XUrl | nvarchar(400) | NULL | Perfil en X / Twitter. |
| 15 | InstagramUrl | nvarchar(400) | NULL | Perfil en Instagram. |
| 16 | FuenteUrl | nvarchar(1000) | NULL | La página donde se encontró a la persona. Es lo que permite auditar un dato que nadie tecleó. |
| Rastro | ||||
| 17 | Origen | nvarchar(40) | NOT NULL · def. 'GlobalLink' | Para que la tabla pueda sostener mañana contactos que no vengan de GlobalLink sin tener que cambiarla. |
| 18 | FechaAlta | datetime | NOT NULL · def. GETDATE() | Cuándo se creó la fila. |
| 19 | FechaModificacion | datetime | NULL | Queda para cuando el addendum permita corregir una persona. Con INSERT-only se queda nula. |
| 20 | idempotency_key | varchar(80) | NULL | Igual que en las otras cuatro tablas, con el mismo índice único filtrado. |
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
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.
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.
NULL, no como cadena vacía: «no lo sabemos» y
«está en blanco» no son lo mismo.+ y código de país.IdContactoGlobalLink es estable: la misma persona conserva su identificador
entre envíos.