# Listas de contactos ## Introducción ### Descripción general El servicio de Listas de contactos permite crear y administrar colecciones de contactos identificados por teléfono y/o correo electrónico. Cada lista pertenece a un propietario y puede contar con usuarios administradores adicionales que pueden tener acceso de solo lectura o de lectura y escritura. El servicio expone una API REST sobre HTTP y admite dos mecanismos de autenticación: sesión de perfil y token de lista. ### Problema que resuelve Proporciona una fuente de datos centralizada y reutilizable de contactos que puede emplearse en procesos de difusión, campañas de mensajería y operaciones similares que requieran iterar o validar destinatarios. Elimina la necesidad de mantener listas de contactos en cada proceso individual. ### Casos de uso - Administración de listas de destinatarios para difusiones por WhatsApp u otros canales. - Validación de destinatarios contra listas de rebotes duros y listas de exclusión (desuscritos). - Importación masiva de contactos desde sistemas externos. - Consulta y filtrado de contactos por teléfono, correo, etiquetas o campos libres. - Delegación de administración de una lista a otros perfiles sin ceder la propiedad. ### Alcance funcional - CRUD completo sobre listas de contactos. - CRUD completo sobre contactos de una lista. - Gestión de usuarios administradores de lista (requiere ser propietario). - Importación masiva de contactos mediante arreglo JSON en una sola solicitud. - Filtrado de contactos por texto libre. - Campo de metadatos extensibles (`extra`) en cada contacto. - Filtrado de listas por propiedad exclusiva mediante parámetro de consulta. --- ## Autenticación El servicio implementa dos mecanismos de autenticación mutuamente excluyentes. La lógica evalúa primero si existe una sesión activa; si no la hay, intenta autenticar por token de lista. ### Sesión autenticada La sesión es establecida previamente por la capa de autorización del sistema (IDP). Se identifica mediante el campo `session/user/ids` del contexto HTTP. Cuando existe un `ids` válido en sesión, cualquier presentación de token en el encabezado `Authorization` es ignorada. La sesión de perfil es el único mecanismo que permite operar sobre el listado global de listas (`GET /contacts/`) y sobre la administración de usuarios de lista. ### API Token Cuando no hay sesión activa, el servicio intenta autenticar la solicitud mediante el token configurado en la lista (`apitoken`). ```http Authorization: Bearer {apitoken} ``` Si el encabezado no incluye el prefijo `Bearer`, el valor completo se trata como el token directamente. **Restricciones del API Token:** - Solo es válido para operaciones sobre la lista específica indicada en la ruta (`list_guid` requerido). - No puede utilizarse para listar todas las listas del propietario (`GET /contacts/`). - No puede utilizarse para operaciones de administración de usuarios de lista. - Otorga acceso equivalente al nivel de escritura (`write`), no permite operaciones reservadas al propietario, como eliminar la lista, eliminar contactos o administrar usuarios. - Si el `apitoken` de la lista es nulo o vacío, la autenticación por token falla con `401`. --- ## Convenciones generales ### Formato de respuesta exitosa Las respuestas exitosas siguen la estructura: ```json { "success": true, "data": {} } ``` El campo `data` puede ser un objeto (para operaciones de lectura individual y escritura) o un arreglo (para operaciones de listado). En operaciones de eliminación, `data` es `null`. ### Formato de error ```json { "success": false, "code": 400, "message": "Descripción del error" } ``` El campo `code` corresponde al código HTTP de error. ### Códigos HTTP utilizados | Código | Situación | |--------|-----------| | 200 | Operación exitosa | | 400 | Datos de entrada inválidos o faltantes | | 401 | Sin autenticación o token inválido | | 403 | Autenticado pero sin permiso para la operación | | 404 | Recurso no encontrado | | 500 | Error interno no controlado | ### GUIDs Todos los recursos exponen un campo `guid` (UUID v4 generado por el servidor en la creación). Las rutas de acceso a recursos individuales utilizan este `guid`. Los campos `id` (clave primaria, entero autonumérico) son devueltos en las respuestas pero no se utilizan en las rutas de la API. ### Permisos El servicio define tres niveles de acceso sobre una lista: | Nivel | Descripción | |-------|-------------| | `owner` | Propietario de la lista. Tiene acceso total: leer, escribir, eliminar la lista, eliminar contactos y administrar usuarios de lista. | | `write` | Administrador con escritura. Puede leer y modificar la lista y sus contactos. No puede eliminar la lista, eliminar contactos ni administrar usuarios. | | `default` | Administrador de solo lectura. Solo puede leer la lista y sus contactos. | La autenticación por API Token equivale funcionalmente al nivel `write` en cuanto a qué operaciones permite, pero no puede ejecutar operaciones que requieran `owner`. --- ## Endpoints El enrutamiento determina el grupo de operaciones según la presencia de los parámetros de ruta `list_guid`, `contact_guid` y `admin_guid`. El método `PUT` es tratado internamente como `PATCH`. Todas las solicitudes se realizan a... **URL BASE**: `https://agent.induxsoft.net/` **Ejemplo**: - **GET** `https://agent.induxsoft.net/contacts/` (Consultar listas accesibles por el perfil) - **GET** `https://agent.induxsoft.net/contacts/{list_guid}/elements/` (Listar contactos) - etc... Ver [resumen de endpoints](#anexo-tabla-resumen-de-endpoints). --- ### GET /contacts/ Obtiene todas las listas accesibles para el perfil en sesión: aquellas de las que es propietario y aquellas en las que está registrado como administrador. **URL:** `https://agent.induxsoft.net/contacts/` **Autenticación:** Sesión requerida. No admite API Token. **Parámetros de consulta** | Parámetro | Tipo | Descripción | |-----------|------|-------------| | `--only-owner` | bool | Si es `true`, devuelve únicamente las listas de las que el perfil es propietario. Por defecto devuelve todas las accesibles. | **Ejemplo de solicitud** ```http GET https://agent.induxsoft.net/contacts/ Authorization: Bearer {ids} ``` **Ejemplo de respuesta** ```json { "success": true, "data": [ { "id": 1, "guid": "a1b2c3d4...", "owner": "perfil_guid...", "name": "Clientes activos", "notes": "Lista para campaña de verano", "apitoken": "tok_xyz...", "access_level": "owner", "owner_name": "Juan García" } ] } ``` **Campos de la respuesta** | Campo | Descripción | |-------|-------------| | `id` | Clave primaria interna. | | `guid` | Identificador global de la lista. | | `owner` | Identificador global del propietario. | | `name` | Nombre de la lista. | | `notes` | Notas. | | `apitoken` | Token de acceso. Devuelto si existe. | | `access_level` | Nivel de acceso del perfil en sesión: `owner`, `write` o `default`. | | `owner_name` | Nombre del propietario resuelto. | --- ### GET /contacts/{list_guid}/ Obtiene las propiedades de una lista específica. **URL:** `https://agent.induxsoft.net/contacts/{list_guid}/` **Autenticación:** Sesión o API Token. Requiere acceso de lectura. **Parámetros de ruta** | Parámetro | Descripción | |-----------|-------------| | `list_guid` | GUID de la lista. | **Ejemplo de solicitud** ```http GET https://agent.induxsoft.net/contacts/a1b2c3d4.../ Authorization: Bearer {ids_o_apitoken} ``` **Ejemplo de respuesta** ```json { "success": true, "data": { "id": 1, "guid": "a1b2c3d4...", "owner": "perfil_guid...", "name": "Clientes activos", "notes": "Lista para campaña de verano", "apitoken": "tok_xyz...", "access_level": "owner", "owner_name": "Juan García" } } ``` --- ### POST /contacts/ Crea una nueva lista de contactos. El propietario es el perfil en sesión. **URL:** `https://agent.induxsoft.net/contacts/` **Autenticación:** Sesión requerida. **Body (JSON)** | Campo | Tipo | Requerido | Descripción | |-------|------|-----------|-------------| | `name` | string | Sí | Nombre de la lista. Máximo 32 caracteres efectivos tras recorte. | | `notes` | string | No | Notas o descripción. | | `apitoken` | string | No | Token de acceso. Si se omite, no se modifica (en la creación queda nulo). | **Ejemplo de solicitud** ```http POST https://agent.induxsoft.net/contacts/ Authorization: Bearer {ids} Content-Type: application/json { "name": "Prospectos 2025", "notes": "Leads de feria", "apitoken": "mi-token-secreto" } ``` **Ejemplo de respuesta** Devuelve el objeto completo de la lista recién creada (equivalente a `GET https://agent.induxsoft.net/contacts/{list_guid}/`). ```json { "success": true, "data": { "id": 7, "guid": "f9e8d7c6...", "owner": "perfil_guid...", "name": "Prospectos 2025", "notes": "Leads de feria", "apitoken": "mi-token-secreto", "access_level": "owner", "owner_name": "Juan García" } } ``` --- ### PUT|PATCH /contacts/{list_guid}/ Actualiza las propiedades de una lista existente. **URL:** `https://agent.induxsoft.net/contacts/{list_guid}/` **Autenticación:** Sesión o API Token. Requiere acceso de escritura. **Parámetros de ruta** | Parámetro | Descripción | |-----------|-------------| | `list_guid` | GUID de la lista a actualizar. | **Body (JSON)** Mismos campos que `POST https://agent.induxsoft.net/contacts/`. **Ejemplo de solicitud** ```http PATCH https://agent.induxsoft.net/contacts/a1b2c3d4.../ Authorization: Bearer {ids_o_apitoken} Content-Type: application/json { "name": "Prospectos 2025 (actualizado)", "notes": "Actualizado post-evento", "apitoken": "" } ``` **Ejemplo de respuesta** Devuelve el objeto actualizado de la lista (equivalente a `GET https://agent.induxsoft.net/contacts/{list_guid}/`). **Consideraciones** - Enviar `apitoken` con valor vacío (`""`) deshabilita la autenticación por token para esa lista. - El campo `owner` no es modificable a través de este endpoint. --- ### DELETE /contacts/{list_guid}/ Elimina una lista de contactos. **URL:** `https://agent.induxsoft.net/contacts/{list_guid}/` **Autenticación:** Sesión requerida. Solo el propietario. **Parámetros de ruta** | Parámetro | Descripción | |-----------|-------------| | `list_guid` | GUID de la lista a eliminar. | **Ejemplo de solicitud** ```http DELETE https://agent.induxsoft.net/contacts/a1b2c3d4.../ Authorization: Bearer {ids} ``` **Ejemplo de respuesta** ```json { "success": true, "data": null } ``` **Consideraciones** La eliminación es física. --- ### GET /contacts/{list_guid}/elements/ Obtiene la lista de contactos de una lista, con soporte de filtro por texto. **URL:** `https://agent.induxsoft.net/contacts/{list_guid}/elements/` **Autenticación:** Sesión o API Token. Requiere acceso de lectura. **Parámetros de ruta** | Parámetro | Descripción | |-----------|-------------| | `list_guid` | GUID de la lista. | **Parámetros de consulta** | Parámetro | Tipo | Descripción | |-----------|------|-------------| | `text` | string | Texto de filtro. Si se proporciona, se aplica búsqueda exacta sobre `phone` y `email`, y búsqueda parcial (`LIKE %text%`) sobre `name` y `tags`. Si se omite o es vacío, devuelve todos los contactos. | **Ejemplo de solicitud** ```http GET https://agent.induxsoft.net/contacts/a1b2c3d4.../elements/?text=vip Authorization: Bearer {ids_o_apitoken} ``` **Ejemplo de respuesta** ```json { "success": true, "data": [ { "id": 10, "guid": "c1d2e3f4...", "name": "María López", "phone": "5212221234567", "email": "maria@ejemplo.com", "tags": "vip,confirmado", "extra": { "empresa": "Acme", "ciudad": "CDMX" } } ] } ``` El campo `extra` se devuelve como objeto JSON, no como cadena. --- ### GET /contacts/{list_guid}/elements/{contact_guid}/ Obtiene la información de un contacto específico. **URL:** `https://agent.induxsoft.net/contacts/{list_guid}/elements/{contact_guid}/` **Autenticación:** Sesión o API Token. Requiere acceso de lectura. **Parámetros de ruta** | Parámetro | Descripción | |-----------|-------------| | `list_guid` | GUID de la lista. | | `contact_guid` | GUID del contacto. | **Ejemplo de solicitud** ```http GET https://agent.induxsoft.net/contacts/a1b2c3d4.../elements/c1d2e3f4.../ Authorization: Bearer {ids_o_apitoken} ``` **Ejemplo de respuesta** ```json { "success": true, "data": { "id": 10, "guid": "c1d2e3f4...", "name": "María López", "phone": "5212221234567", "email": "maria@ejemplo.com", "tags": "vip,confirmado", "extra": { "empresa": "Acme" } } } ``` --- ### POST /contacts/{list_guid}/elements/ Agrega uno o múltiples contactos a una lista. **URL:** `https://agent.induxsoft.net/contacts/{list_guid}/elements/` **Autenticación:** Sesión o API Token. Requiere acceso de escritura. **Parámetros de ruta** | Parámetro | Descripción | |-----------|-------------| | `list_guid` | GUID de la lista. | **Body (JSON) — objeto único** | Campo | Tipo | Requerido | Descripción | |-------|------|-----------|-------------| | `phone` | string | Condicional | Teléfono. Requerido si `email` está vacío. | | `email` | string | Condicional | Correo electrónico. Requerido si `phone` está vacío. | | `name` | string | No | Nombre del contacto. Máximo 50 caracteres. | | `tags` | string | No | Etiquetas separadas por comas. | | `extra` | object o string JSON | No | Campos adicionales. Puede enviarse como objeto o como cadena JSON. | | Campos adicionales | cualquiera | No | Cualquier campo no reconocido se incorpora automáticamente al objeto `extra`. | **Body (JSON) — arreglo para importación masiva** Se puede enviar un arreglo de objetos con la misma estructura anterior. Ver sección [Importación masiva de contactos](#importacion-masiva-de-contactos). **Ejemplo de solicitud (objeto único)** ```http POST https://agent.induxsoft.net/contacts/a1b2c3d4.../elements/ Authorization: Bearer {ids_o_apitoken} Content-Type: application/json { "phone": "5212221234567", "email": "nuevo@ejemplo.com", "name": "Carlos Reyes", "tags": "prospecto", "empresa": "Beta Corp" } ``` En este ejemplo, `empresa` no es un campo estándar y se incorpora automáticamente a `extra`. **Ejemplo de respuesta** Devuelve el objeto del contacto recién creado. ```json { "success": true, "data": { "id": 15, "guid": "e5f6g7h8...", "name": "Carlos Reyes", "phone": "5212221234567", "email": "nuevo@ejemplo.com", "tags": "prospecto", "extra": { "empresa": "Beta Corp" } } } ``` --- ### PUT|PATCH /contacts/{list_guid}/elements/{contact_guid}/ Actualiza un contacto existente. **URL:** `https://agent.induxsoft.net/contacts/{list_guid}/elements/{contact_guid}/` **Autenticación:** Sesión o API Token. Requiere acceso de escritura. **Parámetros de ruta** | Parámetro | Descripción | |-----------|-------------| | `list_guid` | GUID de la lista. | | `contact_guid` | GUID del contacto a actualizar. | **Body (JSON)** Mismos campos que la creación. En actualización, la validación de `phone`/`email` solo aplica si ambos campos están presentes en el body y resultan vacíos simultáneamente. **Ejemplo de solicitud** ```http PATCH https://agent.induxsoft.net/contacts/a1b2c3d4.../elements/e5f6g7h8.../ Authorization: Bearer {ids_o_apitoken} Content-Type: application/json { "tags": "prospecto,contactado" } ``` **Ejemplo de respuesta** Devuelve el objeto actualizado del contacto. **Consideraciones** - En actualizaciones parciales, los campos no incluidos en el body no son modificados. - Si `phone` se envía vacío (`""`), se establece como nulo en la base de datos. - Si `email` se envía vacío (`""`), se establece como nulo en la base de datos. - Los campos no reconocidos se incorporan a `extra`. --- ### DELETE /contacts/{list_guid}/elements/{contact_guid}/ Elimina un contacto de una lista. **URL:** `https://agent.induxsoft.net/contacts/{list_guid}/elements/{contact_guid}/` **Autenticación:** Sesión requerida. Solo el propietario. **Parámetros de ruta** | Parámetro | Descripción | |-----------|-------------| | `list_guid` | GUID de la lista. | | `contact_guid` | GUID del contacto a eliminar. | **Ejemplo de solicitud** ```http DELETE https://agent.induxsoft.net/contacts/a1b2c3d4.../elements/e5f6g7h8.../ Authorization: Bearer {ids} ``` **Ejemplo de respuesta** ```json { "success": true, "data": null } ``` La eliminación es física. --- ### GET /contacts/{list_guid}/admins/ Obtiene la lista de usuarios administradores de una lista. **URL:** `https://agent.induxsoft.net/contacts/{list_guid}/admins/` **Autenticación:** Sesión requerida. Solo el propietario. **Parámetros de ruta** | Parámetro | Descripción | |-----------|-------------| | `list_guid` | GUID de la lista. | **Ejemplo de solicitud** ```http GET https://agent.induxsoft.net/contacts/a1b2c3d4.../admins/ Authorization: Bearer {ids} ``` **Ejemplo de respuesta** ```json { "success": true, "data": [ { "id": 3, "guid": "adm1guid...", "user": "perfil_guid...", "can_write": true, "user_name": "Ana Torres", "user_email": "ana@ejemplo.com", "user_phone": "5219991234567" } ] } ``` --- ### GET /contacts/{list_guid}/admins/{admin_guid}/ Obtiene la información de un administrador específico de una lista. **URL:** `https://agent.induxsoft.net/contacts/{list_guid}/admins/{admin_guid}/` **Autenticación:** Sesión requerida. Solo el propietario. **Parámetros de ruta** | Parámetro | Descripción | |-----------|-------------| | `list_guid` | GUID de la lista. | | `admin_guid` | GUID del registro de administrador (`contact_list_usr.sys_guid`). | **Ejemplo de solicitud** ```http GET https://agent.induxsoft.net/contacts/a1b2c3d4.../admins/adm1guid.../ Authorization: Bearer {ids} ``` **Ejemplo de respuesta** ```json { "success": true, "data": { "id": 3, "guid": "adm1guid...", "user": "perfil_guid...", "can_write": true, "user_name": "Ana Torres", "user_email": "ana@ejemplo.com", "user_phone": "5219991234567" } } ``` --- ### POST /contacts/{list_guid}/admins/ Agrega un administrador a una lista. **URL:** `https://agent.induxsoft.net/contacts/{list_guid}/admins/` **Autenticación:** Sesión requerida. Solo el propietario. **Parámetros de ruta** | Parámetro | Descripción | |-----------|-------------| | `list_guid` | GUID de la lista. | **Body (JSON)** | Campo | Tipo | Requerido | Descripción | |-------|------|-----------|-------------| | `user` | string | Sí | GUID, correo electrónico o número de teléfono móvil del perfil a agregar. | | `can_write` | bool | No | `true` para acceso de escritura. `false` (predeterminado) para solo lectura. | **Ejemplo de solicitud** ```http POST https://agent.induxsoft.net/contacts/a1b2c3d4.../admins/ Authorization: Bearer {ids} Content-Type: application/json { "user": "ana@ejemplo.com", "can_write": true } ``` **Ejemplo de respuesta** Devuelve el objeto del administrador recién creado. ```json { "success": true, "data": { "id": 3, "guid": "adm1guid...", "user": "perfil_guid...", "can_write": true, "user_name": "Ana Torres", "user_email": "ana@ejemplo.com", "user_phone": "5219991234567" } } ``` --- ### PUT|PATCH /contacts/{list_guid}/admins/{admin_guid}/ Actualiza los datos de un administrador de lista (principalmente el permiso `can_write`). **URL:** `https://agent.induxsoft.net/contacts/{list_guid}/admins/{admin_guid}/` **Autenticación:** Sesión requerida. Solo el propietario. **Parámetros de ruta** | Parámetro | Descripción | |-----------|-------------| | `list_guid` | GUID de la lista. | | `admin_guid` | GUID del registro de administrador. | **Body (JSON)** | Campo | Tipo | Requerido | Descripción | |-------|------|-----------|-------------| | `can_write` | bool | No | Nuevo nivel de acceso. | **Ejemplo de solicitud** ```http PATCH https://agent.induxsoft.net/contacts/a1b2c3d4.../admins/adm1guid.../ Authorization: Bearer {ids} Content-Type: application/json { "can_write": false } ``` **Ejemplo de respuesta** Devuelve el objeto actualizado del administrador. --- ### DELETE /contacts/{list_guid}/admins/{admin_guid}/ Elimina a un usuario de la lista de administradores. **URL:** `https://agent.induxsoft.net/contacts/{list_guid}/admins/{admin_guid}/` **Autenticación:** Sesión requerida. Solo el propietario (`RequireOwner`). **Parámetros de ruta** | Parámetro | Descripción | |-----------|-------------| | `list_guid` | GUID de la lista. | | `admin_guid` | GUID del registro de administrador. | **Ejemplo de solicitud** ```http DELETE https://agent.induxsoft.net/contacts/a1b2c3d4.../admins/adm1guid.../ Authorization: Bearer {ids} ``` **Ejemplo de respuesta** ```json { "success": true, "data": null } ``` --- ## Importación masiva de contactos El endpoint `POST /contacts/{list_guid}/elements/` detecta automáticamente si el body es un objeto o un arreglo. Cuando recibe un arreglo JSON, procesa cada elemento de forma individual y devuelve un arreglo de resultados. Los errores en elementos individuales son absorbidos: si un contacto del lote falla (por ejemplo, por duplicado), el error se descarta y el procesamiento continúa con el siguiente elemento. Los contactos creados exitosamente aparecen en la respuesta; los fallidos simplemente no se incluyen. **Ejemplo de solicitud** ```http POST https://agent.induxsoft.net/contacts/a1b2c3d4.../elements/ Authorization: Bearer {ids_o_apitoken} Content-Type: application/json [ { "phone": "5212221230001", "name": "Contacto Uno", "tags": "importado" }, { "phone": "5212221230002", "email": "dos@ejemplo.com", "name": "Contacto Dos" }, { "email": "tres@ejemplo.com" } ] ``` **Ejemplo de respuesta** ```json { "success": true, "data": [ { "id": 20, "guid": "guid-uno...", "name": "Contacto Uno", "phone": "5212221230001", "email": null, "tags": "importado", "extra": {} }, { "id": 21, "guid": "guid-dos...", "name": "Contacto Dos", "phone": "5212221230002", "email": "dos@ejemplo.com", "tags": null, "extra": {} }, { "id": 22, "guid": "guid-tres...", "name": null, "phone": null, "email": "tres@ejemplo.com", "tags": null, "extra": {} } ] } ``` Si todos los elementos fallan, la respuesta devuelve un arreglo vacío con `success: true`. --- ## Reglas de negocio ### Campos obligatorios - Al crear una lista: `name` es requerido. El nombre se recorta a 32 caracteres y se eliminan espacios al inicio y final. Un nombre vacío tras el recorte produce error `400`. - Al crear un contacto: al menos uno de `phone` o `email` debe ser no vacío. - Al crear un administrador: el campo `user` es requerido y debe corresponder a un perfil de Induxsoft existente. ### Restricciones de duplicidad - Nombres de lista: un propietario no puede tener dos listas con el mismo nombre (índice único `owner` + `name`). - Contactos: dentro de una misma lista, no puede haber dos contactos con el mismo `phone` (índice único `list` + `phone`) ni con el mismo `email` (índice único `list` + `email`). Los intentos de crear duplicados resultarán en error de base de datos. - Administradores: un perfil no puede ser administrador de la misma lista más de una vez (índice único `list` + `user`). ### Reglas de eliminación - La eliminación de una lista es física. - La eliminación de contactos es física. - La eliminación de administradores es física. - Solo el propietario puede eliminar una lista, un contacto o un administrador. ### Restricciones de permisos | Operación | Nivel mínimo requerido | |-----------|----------------------| | Listar todas las listas propias | Sesión (propietario o administrador) | | Leer propiedades de una lista | Sesión o API Token (cualquier nivel) | | Crear lista | Sesión | | Actualizar lista | Sesión o API Token (nivel `write` o propietario) | | Eliminar lista | Sesión (solo propietario) | | Listar contactos | Sesión o API Token (cualquier nivel) | | Leer contacto | Sesión o API Token (cualquier nivel) | | Crear contacto | Sesión o API Token (nivel `write` o propietario) | | Actualizar contacto | Sesión o API Token (nivel `write` o propietario) | | Eliminar contacto | Sesión (solo propietario) | | Listar administradores | Sesión (solo propietario) | | Leer administrador | Sesión (solo propietario) | | Crear administrador | Sesión (solo propietario) | | Actualizar administrador | Sesión (solo propietario) | | Eliminar administrador | Sesión (solo propietario) | ### Validaciones de formato - El campo `user` en administradores acepta GUID, correo electrónico o número de teléfono móvil. Si no hay coincidencia, error `404`. - Los campos `phone` y `email` de contactos no tienen validación de formato en la capa de servicio; se almacenan tal como se reciben (con `trim`). - `name` del contacto se recorta a 50 caracteres; `name` de la lista se recorta a 32 caracteres. ### Comportamientos especiales del campo `extra` - Cualquier campo del body de un contacto que no sea `name`, `phone`, `email`, `tags` o `extra` se incorpora automáticamente al objeto `extra`. - El campo `extra` puede enviarse como objeto JSON o como cadena JSON; ambas formas son aceptadas. - Al leer contactos, `extra` siempre se devuelve como objeto (nunca como cadena). Si el valor almacenado es nulo o inválido, se devuelve `{}`. ### Actualización parcial de contactos - Los campos no enviados en el body de una actualización no son modificados. - Si `phone` o `email` se envían explícitamente como cadena vacía, el campo se establece como nulo en la base de datos. - Si `name` o `tags` no están presentes en el body, no se modifican. --- ## Errores | Código | Mensaje representativo | Causa | |--------|------------------------|-------| | 400 | El nombre de la lista es requerido. | `name` vacío o ausente al crear/actualizar una lista. | | 400 | El contacto debe tener al menos teléfono o correo. | Ambos campos vacíos al crear o al actualizar con ambos campos presentes. | | 400 | El perfil es requerido. | Campo `user` vacío al crear/actualizar un administrador. | | 401 | No se proporciono el token de autenticación. | Header `Authorization` vacío o ausente sin sesión activa. | | 401 | Se requiere identificador de lista para autenticación por token. | Intento de autenticación por token sin `list_guid` en la ruta. | | 401 | No autorizado. | Token enviado no coincide con el `apitoken` de la lista. | | 401 | Método de autenticación no implementado. | Header `Authorization` con prefijo distinto de `bearer`. | | 401 | Esta operación requiere autenticación por sesión. | Operación que requiere sesión invocada sin sesión activa. | | 403 | No se pudo determinar el perfil del usuario. | `uid` del perfil vacío en la sesión al verificar acceso. | | 403 | Esta operación está reservada al propietario de la lista. | Operación `RequireOwner` ejecutada por un no propietario. | | 403 | No tiene acceso a esta lista. | Perfil sin registro en `contact_list_usr` intentando acceder a una lista ajena. | | 403 | No tiene permiso de escritura en esta lista. | Administrador de solo lectura intentando escribir. | | 404 | Lista no encontrada. | `list_guid` no corresponde a ninguna lista activa. | | 404 | Contacto no encontrado. | `contact_guid` no corresponde a ningún contacto en la lista indicada. | | 404 | Administrador no encontrado. | `admin_guid` no corresponde a ningún administrador en la lista indicada. | | 404 | Perfil no encontrado. | El valor de `user` no resuelve ningún perfil de Induxsoft. | | 500 | (mensaje de excepción interna) | Error no controlado. | --- ## Anexo: Tabla resumen de endpoints | Método | Ruta | Descripción | Permisos | |--------|------|-------------|----------| | GET | `/contacts/` | Listar listas accesibles por el perfil | Sesión | | GET | `/contacts/{list_guid}/` | Leer propiedades de una lista | Sesión o Token | | POST | `/contacts/` | Crear lista | Sesión | | PUT|PATCH | `/contacts/{list_guid}/` | Actualizar lista | Sesión o Token (write) | | DELETE | `/contacts/{list_guid}/` | Eliminar lista | Sesión (owner) | | GET | `/contacts/{list_guid}/elements/` | Listar contactos (con filtro opcional) | Sesión o Token | | GET | `/contacts/{list_guid}/elements/{contact_guid}/` | Leer contacto | Sesión o Token | | POST | `/contacts/{list_guid}/elements/` | Crear contacto o importación masiva | Sesión o Token (write) | | PUT|PATCH | `/contacts/{list_guid}/elements/{contact_guid}/` | Actualizar contacto | Sesión o Token (write) | | DELETE | `/contacts/{list_guid}/elements/{contact_guid}/` | Eliminar contacto | Sesión (owner) | | GET | `/contacts/{list_guid}/admins/` | Listar administradores | Sesión (owner) | | GET | `/contacts/{list_guid}/admins/{admin_guid}/` | Leer administrador | Sesión (owner) | | POST | `/contacts/{list_guid}/admins/` | Agregar administrador | Sesión (owner) | | PUT|PATCH | `/contacts/{list_guid}/admins/{admin_guid}/` | Actualizar administrador | Sesión (owner) | | DELETE | `/contacts/{list_guid}/admins/{admin_guid}/` | Eliminar administrador | Sesión (owner) |