> ## Documentation Index
> Fetch the complete documentation index at: https://docs.firma.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Configuración del Espacio de Trabajo

> Configura los ajustes a nivel de espacio de trabajo, incluyendo plantillas de correo, información del equipo y preferencias de zona horaria.

La configuración del espacio de trabajo te permite personalizar las plantillas de correo electrónico, la información de contacto del equipo y las preferencias de zona horaria a nivel de espacio de trabajo. Estos ajustes se aplican a todas las solicitudes de firma y plantillas dentro del espacio de trabajo.

## Casos de uso

* **Personalización de correos**: Personaliza los encabezados y el texto del cuerpo de los correos de invitación a firma
* **Información de contacto del equipo**: Establece un correo del equipo para preguntas de soporte de los destinatarios
* **Gestión de zona horaria**: Configura la zona horaria para las visualizaciones de fecha/hora y recordatorios
* **Aplicaciones multi-tenant**: Separa la configuración por espacio de trabajo para soluciones de marca blanca

<Note>
  Consulta la guía sobre [Límites de tasa](/guides/rate-limits).
</Note>

***

## Obtener configuración del espacio de trabajo

Recupera la configuración actual del espacio de trabajo, incluyendo plantillas de correo, correo del equipo y configuración de zona horaria.

### Endpoint

```
GET /workspace/{workspace_id}/settings
```

### Parámetros

* `workspace_id` (string, requerido) - UUID del espacio de trabajo

### Ejemplo - cURL

```bash theme={null}
curl -X GET "https://api.firma.dev/functions/v1/signing-request-api/workspace/123e4567-e89b-12d3-a456-426614174000/settings" \
 -H "Authorization: Bearer YOUR_API_KEY"
```

### Respuesta (200 OK)

```json theme={null}
{
  "workspace_id": "123e4567-e89b-12d3-a456-426614174000",
  "signing_request_email_header": "You've been invited to sign a document",
  "signing_request_email_body": "Please review and sign the document at your earliest convenience. If you have any questions, contact our team.",
  "team_email": "support@yourcompany.com",
  "timezone": "America/New_York"
}
```

<Note>
  La respuesta incluye campos adicionales más allá de la configuración de correo: `show_qr_code`, `require_otp_verification`, `require_terms_acceptance`, `allow_presigning_download`, configuraciones de color, `signing_button_label_overrides`, configuraciones de la página de finalización, y más. Esta guía se enfoca en el subconjunto de plantillas de correo y marca. Consulta la [referencia de API](/api-reference/v01.33.00/workspace-settings/get-workspace-settings) para el esquema completo de respuesta.
</Note>

### Encabezados de límite de tasa

```
X-RateLimit-Limit: 200
X-RateLimit-Remaining: 199
X-RateLimit-Reset: 2026-08-07T12:35:00.000Z
```

***

## Actualizar configuración del espacio de trabajo

Actualiza la configuración del espacio de trabajo. Puedes actualizar uno o más campos; solo los campos proporcionados serán actualizados.

### Endpoint

```
PUT /workspace/{workspace_id}/settings
```

### Parámetros

* `workspace_id` (string, requerido) - UUID del espacio de trabajo

### Cuerpo de la solicitud

Todos los campos son opcionales; incluye solo los campos que deseas actualizar:

```json theme={null}
{
  "signing_request_email_header": "You've been invited to sign a document",
  "signing_request_email_body": "Please review and sign the document at your earliest convenience.",
  "team_email": "support@yourcompany.com",
  "timezone": "America/New_York"
}
```

### Descripción de campos

* `signing_request_email_header` (string, opcional) - Texto personalizado del encabezado para correos de firma (máximo 500 caracteres)
* `signing_request_email_body` (string, opcional) - Texto personalizado del cuerpo para correos de firma (máximo 50000 caracteres)
* `team_email` (string, opcional) - Dirección de correo electrónico válida para soporte del destinatario
* `timezone` (string, opcional) - Identificador de zona horaria IANA

### Ejemplo - cURL

```bash theme={null}
curl -X PUT "https://api.firma.dev/functions/v1/signing-request-api/workspace/123e4567-e89b-12d3-a456-426614174000/settings" \
 -H "Authorization: Bearer YOUR_API_KEY" \
 -H "Content-Type: application/json" \
 -d '{
    "signing_request_email_header": "Action Required: Sign Your Agreement",
    "signing_request_email_body": "Hello! We need your signature on an important document. Please click the link below to review and sign. Contact us at support@acmecorp.com if you have questions.",
    "team_email": "support@acmecorp.com",
    "timezone": "America/Los_Angeles"
  }'
```

### Respuesta (200 OK)

Devuelve la configuración actualizada del espacio de trabajo:

```json theme={null}
{
  "workspace_id": "123e4567-e89b-12d3-a456-426614174000",
  "signing_request_email_header": "Action Required: Sign Your Agreement",
  "signing_request_email_body": "Hello! We need your signature on an important document. Please click the link below to review and sign. Contact us at support@acmecorp.com if you have questions.",
  "team_email": "support@acmecorp.com",
  "timezone": "America/Los_Angeles"
}
```

### Encabezados de límite de tasa

```
X-RateLimit-Limit: 120
X-RateLimit-Remaining: 119
X-RateLimit-Reset: 2026-08-07T12:35:00.000Z
```

***

## Ejemplos de implementación

### Node.js (Express) - Obtener configuración

```js theme={null}
import express from 'express'
import fetch from 'node-fetch'

const app = express()
const FIRMA_API_BASE = process.env.FIRMA_API_BASE || 'https://api.firma.dev/functions/v1/signing-request-api'
const API_KEY = process.env.FIRMA_API_KEY

app.get('/api/workspace/:workspaceId/settings', async (req, res) => {
  const { workspaceId } = req.params
  
  try {
    const response = await fetch(
      `${FIRMA_API_BASE}/workspace/${workspaceId}/settings`,
      {
        headers: {
          'Authorization': `Bearer ${API_KEY}`
        }
      }
    )
    
    if (!response.ok) {
      const error = await response.text()
      return res.status(response.status).json({ error })
    }
    
    const settings = await response.json()
    res.json(settings)
  } catch (error) {
    console.error('Error al obtener la configuración del espacio de trabajo:', error)
    res.status(500).json({ error: 'Error al recuperar la configuración' })
  }
})

app.listen(3000)
```

### Node.js (Express) - Actualizar configuración

```js theme={null}
app.put('/api/workspace/:workspaceId/settings', express.json(), async (req, res) => {
  const { workspaceId } = req.params
  const { signing_request_email_header, signing_request_email_body, team_email, timezone } = req.body
  
  // Validar entrada
  if (team_email && !isValidEmail(team_email)) {
    return res.status(400).json({ error: 'Formato de correo inválido' })
  }
  
  try {
    const response = await fetch(
      `${FIRMA_API_BASE}/workspace/${workspaceId}/settings`,
      {
        method: 'PUT',
        headers: {
          'Authorization': `Bearer ${API_KEY}`,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({
          signing_request_email_header,
          signing_request_email_body,
          team_email,
          timezone
        })
      }
    )
    
    if (!response.ok) {
      const error = await response.text()
      return res.status(response.status).json({ error })
    }
    
    const settings = await response.json()
    res.json(settings)
  } catch (error) {
    console.error('Error al actualizar la configuración del espacio de trabajo:', error)
    res.status(500).json({ error: 'Error al actualizar la configuración' })
  }
})

function isValidEmail(email) {
  return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)
}
```

### Python (Flask) - Obtener configuración

```py theme={null}
from flask import Flask, jsonify
import os
import requests

app = Flask(__name__)

FIRMA_API_BASE = os.getenv('FIRMA_API_BASE', 'https://api.firma.dev/functions/v1/signing-request-api')
API_KEY = os.getenv('FIRMA_API_KEY')

@app.route('/api/workspace/<workspace_id>/settings', methods=['GET'])
def get_workspace_settings(workspace_id):
    try:
        response = requests.get(
            f'{FIRMA_API_BASE}/workspace/{workspace_id}/settings',
            headers={
                'Authorization': f'Bearer {API_KEY}'
            }
        )
        response.raise_for_status()
        return jsonify(response.json())
    except requests.exceptions.RequestException as e:
        return jsonify({'error': 'Error al recuperar la configuración'}), 500

if __name__ == '__main__':
    app.run(port=3000)
```

### Python (Flask) - Actualizar configuración

```py theme={null}
from flask import request
import re

@app.route('/api/workspace/<workspace_id>/settings', methods=['PUT'])
def update_workspace_settings(workspace_id):
    data = request.json
    
    # Validar correo si se proporcionó
    if 'team_email' in data:
        if not re.match(r'^[^\s@]+@[^\s@]+\.[^\s@]+$', data['team_email']):
            return jsonify({'error': 'Formato de correo inválido'}), 400
    
    try:
        response = requests.put(
            f'{FIRMA_API_BASE}/workspace/{workspace_id}/settings',
            headers={
                'Authorization': f'Bearer {API_KEY}',
                'Content-Type': 'application/json'
            },
            json={
                'signing_request_email_header': data.get('signing_request_email_header'),
                'signing_request_email_body': data.get('signing_request_email_body'),
                'team_email': data.get('team_email'),
                'timezone': data.get('timezone')
            }
        )
        response.raise_for_status()
        return jsonify(response.json())
    except requests.exceptions.RequestException as e:
        return jsonify({'error': 'Error al actualizar la configuración'}), 500
```

### React - Componente de gestión de configuración

```jsx theme={null}
import { useState, useEffect } from 'react'

function WorkspaceSettings({ workspaceId }) {
  const [settings, setSettings] = useState(null)
  const [loading, setLoading] = useState(true)
  const [saving, setSaving] = useState(false)
  const [error, setError] = useState(null)
  
  // Cargar configuración actual
  useEffect(() => {
    async function loadSettings() {
      try {
        const response = await fetch(`/api/workspace/${workspaceId}/settings`)
        if (!response.ok) throw new Error('Error al cargar la configuración')
        const data = await response.json()
        setSettings(data)
      } catch (err) {
        setError(err.message)
      } finally {
        setLoading(false)
      }
    }
    loadSettings()
  }, [workspaceId])
  
  // Actualizar configuración
  async function handleSave(updatedSettings) {
    setSaving(true)
    setError(null)
    
    try {
      const response = await fetch(`/api/workspace/${workspaceId}/settings`, {
        method: 'PUT',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(updatedSettings)
      })
      
      if (!response.ok) throw new Error('Error al guardar la configuración')
      
      const data = await response.json()
      setSettings(data)
    } catch (err) {
      setError(err.message)
    } finally {
      setSaving(false)
    }
  }
  
  if (loading) return <div>Cargando configuración...</div>
  if (error) return <div>Error: {error}</div>
  
  return (
    <div>
      <h2>Configuración del Espacio de Trabajo</h2>
      
      <form onSubmit={(e) => {
        e.preventDefault()
        const formData = new FormData(e.target)
        handleSave({
          email_header: formData.get('email_header'),
          email_body: formData.get('email_body'),
          team_email: formData.get('team_email'),
          timezone: formData.get('timezone')
        })
      }}>
        <div>
          <label>Encabezado del correo</label>
          <input
            name="signing_request_email_header"
            defaultValue={settings.email_header}
            maxLength={500}
          />
        </div>
        
        <div>
          <label>Cuerpo del correo</label>
          <textarea
            name="signing_request_email_body"
            defaultValue={settings.email_body}
            maxLength={50000}
            rows={5}
          />
        </div>
        
        <div>
          <label>Correo del equipo</label>
          <input
            name="team_email"
            type="email"
            defaultValue={settings.team_email}
          />
        </div>
        
        <div>
          <label>Zona horaria</label>
          <select name="timezone" defaultValue={settings.timezone}>
            <option value="America/New_York">Hora del Este</option>
            <option value="America/Chicago">Hora Central</option>
            <option value="America/Denver">Hora de Montaña</option>
            <option value="America/Los_Angeles">Hora del Pacífico</option>
            <option value="Europe/London">Londres</option>
            <option value="Europe/Paris">París</option>
            <option value="Asia/Tokyo">Tokio</option>
            {/* Agrega más zonas horarias según sea necesario */}
          </select>
        </div>
        
        <button type="submit" disabled={saving}>
          {saving ? 'Guardando...' : 'Guardar configuración'}
        </button>
      </form>
    </div>
  )
}
```

***

## Personalización de plantillas de correo

Firma admite dos niveles de personalización de correo que comparten el mismo motor de marcadores: los campos `signing_request_email_header` / `signing_request_email_body` en este endpoint de configuración (que se aplican a los correos de invitación a firma y de siguiente firmante, incluyendo reenvíos manuales de ambos), y un editor de Plantillas de Correo más completo por tipo en la página de Configuración del espacio de trabajo, que permite personalizar el asunto y el cuerpo de forma independiente para cada tipo de correo: invitación, siguiente firmante, expiración, cancelación, rechazo, finalización y notificaciones de cambio de identidad.

### Referencia de variables de plantilla

| Variable | Se resuelve como | Categoría |
| - | - | - |
| `{{signer_first_name}}` | Nombre del firmante | Firmante |
| `{{signer_last_name}}` | Apellido del firmante | Firmante |
| `{{signer_name}}` | Nombre completo del firmante | Firmante |
| `{{signer_email}}` | Dirección de correo del firmante | Firmante |
| `{{signer_title}}` | Cargo del firmante (solo se completa en correos de recordatorio; se resuelve vacío en todos los demás tipos) | Firmante |
| `{{signer_company}}` | Empresa del firmante (solo se completa en correos de recordatorio; se resuelve vacío en todos los demás tipos) | Firmante |
| `{{signing_request_name}}` | Nombre de la solicitud de firma | Documento |
| `{{signing_link}}` | URL para que el firmante abra y firme | Documento |
| `{{expiration_date}}` | Fecha de expiración de la solicitud de firma | Documento |
| `{{download_link}}` | Enlace para descargar el documento firmado | Documento |
| `{{decliner_name}}` | Nombre del firmante que rechazó | Documento |
| `{{decline_reason}}` | Razón proporcionada al rechazar | Documento |
| `{{team_name}}` / `{{workspace_name}}` | Nombre del espacio de trabajo (alias, mismo valor) | Equipo |
| `{{team_email}}` / `{{workspace_email}}` | Correo de contacto del espacio de trabajo (alias, mismo valor) | Equipo |
| `{{company_name}}` | Nombre de la empresa | Equipo |
| `{{company_logo}}` | Etiqueta `<img>` con el logo del espacio de trabajo o empresa | Marca |
| `{{signing_qr_code}}` | Etiqueta `<img>` con un código QR que enlaza a la página de firma | Marca |

<Note>
  Los marcadores no distinguen entre mayúsculas y minúsculas y también aceptan la sintaxis legacy de `[corchetes]` (por ejemplo `[signer_name]`) junto con la sintaxis de `{{llaves}}`. Un marcador sin valor para un correo dado simplemente se resuelve como vacío; las plantillas nunca muestran un `{{variable_faltante}}` sin procesar.
</Note>

### Disponibilidad de variables por tipo de correo

Las variables de firmante, documento, equipo y empresa se resuelven para todos los tipos de correo. Tres variables son la excepción:

| Variable | Disponible en |
| - | - |
| `{{signing_qr_code}}` | Solo en correos de invitación a firma y de siguiente firmante |
| `{{decliner_name}}` / `{{decline_reason}}` | Solo en correos de notificación de rechazo (tanto la versión para el firmante como la del administrador) |
| `{{download_link}}` | Solo en correos de finalización |

<Note>
  Los campos `signing_request_email_header` / `signing_request_email_body` en este endpoint de configuración solo afectan a los correos de invitación y de siguiente firmante. Para personalizar los correos de expiración, cancelación, rechazo, finalización o cambio de identidad, utiliza el editor de Plantillas de Correo por tipo en la página de Configuración del espacio de trabajo.
</Note>

### Logo de la empresa (`{{company_logo}}`)

`{{company_logo}}` se resuelve a través de una cadena de respaldo:

1. **Logo del espacio de trabajo**: se usa si el espacio de trabajo tiene su propio logo cargado (se renderiza con el nombre del espacio de trabajo como texto `alt` de la imagen).
2. **Logo de la empresa**: de lo contrario, recurre al logo de la empresa matriz.
3. **Oculto**: si ninguno está configurado, el marcador se resuelve como vacío; no se renderiza ninguna imagen rota.

La imagen del logo se sirve a través de un proxy público de logos y está limitada a `max-width: 200px; max-height: 120px`. El límite de altura evita que logos inusualmente altos empujen el resto del correo debajo del pliegue.

### Código QR en correos (`{{signing_qr_code}}`)

<Warning>
  Los códigos QR en correos se renderizan como **PNG**, no SVG. Gmail elimina completamente las etiquetas `<img>` que apuntan a SVG, y el motor de renderizado basado en Word de Outlook tampoco las muestra; PNG es el formato que se renderiza de forma confiable en todos los clientes de correo.
</Warning>

`{{signing_qr_code}}` solo se completa en los correos de invitación a firma y de siguiente firmante. Permite al destinatario escanear el código para continuar firmando en otro dispositivo en lugar de hacer clic en un enlace. Si se muestra o no, se controla mediante la configuración `show_qr_code` que se propaga en cascada:

1. Configuración a nivel de solicitud de firma (si se estableció explícitamente)
2. Configuración a nivel de espacio de trabajo: `show_qr_code` en este endpoint de configuración
3. Valor predeterminado a nivel de empresa

Establece `show_qr_code` como `true` o `false` a nivel de espacio de trabajo mediante `PUT /workspace/{workspace_id}/settings`, o déjalo sin establecer (`null`) para heredar el valor predeterminado de la empresa.

### Correo del espacio de trabajo (`{{team_email}}` / `{{workspace_email}}`)

`team_email` es un campo a nivel de espacio de trabajo, configurado en este endpoint de configuración (o desde la página de Configuración del espacio de trabajo, bajo **Correo de Contacto del Equipo**). Si no se establece, recurre a `support@firma.dev`.

<Note>
  `team_email` es solo un valor de visualización; se sustituye donde sea que `{{team_email}}` o `{{workspace_email}}` aparezca en una plantilla. **No** se usa como la dirección `Reply-To` del correo; las respuestas de los destinatarios van a la dirección de envío de Firma, no a `team_email`.
</Note>

`team_email` también tiene un segundo rol no relacionado: para las notificaciones de cambio de identidad, es el destinatario real. Firma envía un correo a tu equipo a esta dirección cuando un firmante cambia su nombre durante el flujo, recurriendo al correo del propietario de la cuenta si `team_email` no está configurado.

### Mejores prácticas

**Encabezado del correo** (máximo 500 caracteres):

* Mantenlo conciso y orientado a la acción
* Indica claramente el propósito ("Firma tu acuerdo", "Revisa el documento")
* Evita texto genérico como "Tienes una notificación"

**Cuerpo del correo** (máximo 50000 caracteres):

* Explica qué necesita hacer el destinatario
* Incluye información de contacto de soporte
* Establece expectativas (urgencia, fecha límite si aplica)
* Mantén un tono profesional pero amigable

### Ejemplos de plantillas

**Servicios profesionales**:

```
Encabezado: "Acción requerida: Revisa y firma tu contrato"
Cuerpo: "Gracias por elegir nuestros servicios. Por favor revisa y firma el contrato adjunto a tu conveniencia. Si tienes preguntas o inquietudes, contáctanos en contratos@empresa.com o llama al (555) 123-4567."
```

**Bienes raíces**:

```
Encabezado: "Tus documentos de propiedad están listos para firmar"
Cuerpo: "Tus documentos están listos para firma electrónica. Por favor revísalos cuidadosamente antes de firmar. Contacta a tu agente en agente@inmobiliaria.com si necesitas aclaración sobre algún término."
```

**Incorporación de RRHH**:

```
Encabezado: "¡Bienvenido a [Empresa]! Completa tus documentos de incorporación"
Cuerpo: "¡Bienvenido al equipo! Como parte de tu incorporación, por favor revisa y firma los documentos adjuntos. Si tienes preguntas, escríbenos a rrhh@empresa.com. ¡Estamos emocionados de tenerte a bordo!"
```

**Genérico/flexible**:

```
Encabezado: "Documento listo para tu firma"
Cuerpo: "Un documento requiere tu firma. Por favor revisa el contenido y firma electrónicamente. Contáctanos en soporte@empresa.com si tienes preguntas."
```

***

## Zonas horarias compatibles

La configuración del espacio de trabajo admite todos los identificadores de zona horaria IANA. Zonas horarias comunes:

### Estados Unidos

* `America/New_York` - Hora del Este
* `America/Chicago` - Hora Central
* `America/Denver` - Hora de Montaña
* `America/Los_Angeles` - Hora del Pacífico
* `America/Anchorage` - Hora de Alaska
* `Pacific/Honolulu` - Hora de Hawái

### Europa

* `Europe/London` - GMT/BST
* `Europe/Paris` - Hora de Europa Central
* `Europe/Berlin` - Hora de Europa Central
* `Europe/Madrid` - Hora de Europa Central
* `Europe/Rome` - Hora de Europa Central

### Asia Pacífico

* `Asia/Tokyo` - Hora Estándar de Japón
* `Asia/Shanghai` - Hora Estándar de China
* `Asia/Singapore` - Hora de Singapur
* `Asia/Dubai` - Hora Estándar del Golfo
* `Australia/Sydney` - Hora del Este de Australia

### Américas

* `America/Toronto` - Hora del Este (Canadá)
* `America/Vancouver` - Hora del Pacífico (Canadá)
* `America/Mexico_City` - Hora Central (México)
* `America/Sao_Paulo` - Hora de Brasilia

[Lista completa de zonas horarias IANA](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones)

***

## Límites de tasa

### Obtener configuración del espacio de trabajo

* **Límite**: 200 solicitudes por minuto
* **Caso de uso**: Lecturas frecuentes para visualizaciones de panel
* **Recomendación**: Almacena en caché la configuración del lado del cliente durante 5-10 minutos

### Actualizar configuración del espacio de trabajo

* **Límite**: 120 solicitudes por minuto
* **Caso de uso**: Cambios de configuración del administrador
* **Recomendación**: Aplica debounce a las actualizaciones en la interfaz (espera 1-2 segundos después de que el usuario deje de escribir)

### Encabezados de límite de tasa

Cada respuesta incluye:

```
X-RateLimit-Limit: 200          # Máximo de solicitudes por minuto
X-RateLimit-Remaining: 195       # Restantes en la ventana actual
X-RateLimit-Reset: 2026-08-07T12:35:00.000Z    # Marca de tiempo de reinicio
```

### Manejo de límites de tasa

Si excedes el límite:

```
429 Too Many Requests
```

**Mejores prácticas**:

* Implementa caché del lado del cliente
* Aplica debounce a las actualizaciones frecuentes
* Verifica `X-RateLimit-Remaining` antes de hacer solicitudes
* Implementa retroceso exponencial para reintentos

***

## Respuestas de error

### 400 Bad Request - Error de validación

Datos de entrada inválidos (por ejemplo, correo mal formado, zona horaria inválida):

```json theme={null}
{
  "error": "Invalid email format",
  "code": "VALIDATION_ERROR"
}
```

### 401 Unauthorized

Clave API inválida o faltante:

```json theme={null}
{
  "error": "Invalid API key",
  "code": "INVALID_API_KEY"
}
```

### 403 Forbidden

No tienes acceso a este espacio de trabajo (empresa diferente o permisos insuficientes):

```json theme={null}
{
  "error": "Access denied",
  "code": "ACCESS_DENIED"
}
```

### 404 Not Found

El espacio de trabajo no existe o fue eliminado:

```json theme={null}
{
  "error": "Workspace not found",
  "code": "NOT_FOUND"
}
```

### 429 Too Many Requests

Límite de tasa excedido:

```json theme={null}
{
  "error": "Rate limit exceeded",
  "code": "RATE_LIMIT_EXCEEDED",
  "details": {
    "limit": 120,
    "window": "1m",
    "current_count": 121,
    "retry_after_seconds": 45
  }
}
```

Verifica el encabezado `X-RateLimit-Reset` (marca de tiempo ISO 8601) para saber cuándo puedes reintentar.

***

## Mejores prácticas multi-tenant

Para aplicaciones multi-tenant (múltiples espacios de trabajo):

### 1. Almacenar configuración en caché por espacio de trabajo

```js theme={null}
const settingsCache = new Map()
const CACHE_TTL = 5 * 60 * 1000 // 5 minutos

async function getWorkspaceSettings(workspaceId) {
  const cached = settingsCache.get(workspaceId)
  if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
    return cached.settings
  }
  
  const settings = await fetchWorkspaceSettings(workspaceId)
  settingsCache.set(workspaceId, {
    settings,
    timestamp: Date.now()
  })
  
  return settings
}
```

### 2. Validar acceso al espacio de trabajo

Siempre verifica que el usuario autenticado tenga acceso al espacio de trabajo:

```js theme={null}
async function updateSettings(userId, workspaceId, updates) {
  // Verificar que el usuario tiene acceso de administrador al espacio de trabajo
  const hasAccess = await checkWorkspaceAccess(userId, workspaceId, 'admin')
  if (!hasAccess) {
    throw new Error('Prohibido: Permisos insuficientes')
  }
  
  // Actualizar configuración
  return await updateWorkspaceSettings(workspaceId, updates)
}
```

### 3. Registro de auditoría

Registra todos los cambios de configuración para cumplimiento:

```js theme={null}
async function updateWithAudit(workspaceId, userId, updates) {
  const oldSettings = await getWorkspaceSettings(workspaceId)
  const newSettings = await updateWorkspaceSettings(workspaceId, updates)
  
  // Registrar el cambio
  await auditLog.create({
    workspace_id: workspaceId,
    user_id: userId,
    action: 'workspace_settings_updated',
    changes: {
      old: oldSettings,
      new: newSettings
    },
    timestamp: new Date()
  })
  
  return newSettings
}
```

### 4. Configuración predeterminada al crear espacios de trabajo

Establece valores predeterminados razonables al crear nuevos espacios de trabajo:

```js theme={null}
async function createWorkspace(name, ownerId) {
  const workspace = await db.workspaces.create({ name, owner_id: ownerId })
  
  // Establecer configuración predeterminada
  await updateWorkspaceSettings(workspace.id, {
    email_header: "Has sido invitado a firmar un documento",
    email_body: "Por favor revisa y firma el documento a tu conveniencia.",
    team_email: "soporte@tuempresa.com",
    timezone: "America/New_York"
  })
  
  return workspace
}
```

***

## Solución de problemas

### La configuración no se aplica a los correos

**Síntoma**: La configuración actualizada no aparece en los correos de firma

**Posibles causas**:

* La caché de plantillas de correo no se limpió
* Se usó un ID de espacio de trabajo incorrecto
* Las actualizaciones no se guardaron (verifica la respuesta de la API)

**Solución**:

* Verifica que la actualización fue exitosa (comprueba la respuesta 200)
* Prueba con una nueva solicitud de firma (no un borrador existente)
* Verifica que el ID del espacio de trabajo coincida con la solicitud de firma

### Error de zona horaria inválida

**Síntoma**: Error 400 al establecer la zona horaria

**Solución**: Usa identificadores de zona horaria IANA (por ejemplo, `America/New_York`). La API solo valida el formato (letras, guiones bajos, barras); abreviaturas como `EST` pasan la validación pero pueden no comportarse correctamente para las transiciones de horario de verano. Siempre usa el nombre completo de la zona IANA.

### Error de validación del correo del equipo

**Síntoma**: Error 400 al actualizar el correo del equipo

**Solución**: Asegúrate de que el formato del correo sea válido (contiene @ y dominio)

### Límite de tasa excedido

**Síntoma**: Errores 429 al actualizar la configuración

**Solución**:

* Implementa debounce en los campos de formulario
* Almacena la configuración en caché del lado del cliente
* Espera hasta `X-RateLimit-Reset` antes de reintentar

<Note>
  Consulta la guía sobre [Límites de tasa](/guides/rate-limits).
</Note>

***

## Referencia de API

Para detalles completos sobre operaciones de espacios de trabajo, consulta:

### Gestión de Espacios de Trabajo

* [Listar espacios de trabajo](../api-reference/v01.15.00/workspaces/list-workspaces) - Obtener todos los espacios de trabajo (200 sol/min)
* [Crear espacio de trabajo](../api-reference/v01.15.00/workspaces/create-a-new-workspace) - Crear nuevo espacio de trabajo (120 sol/min)
* [Actualizar espacio de trabajo](../api-reference/v01.15.00/workspaces/update-a-workspace) - Actualizar detalles del espacio de trabajo (120 sol/min)

### Configuración del Espacio de Trabajo

* [Obtener configuración del espacio de trabajo](../api-reference/v01.15.00/workspace-settings/get-workspace-settings) - Recuperar configuración actual (200 sol/min)
* [Actualizar configuración del espacio de trabajo](../api-reference/v01.15.00/workspace-settings/update-workspace-settings) - Actualizar plantillas de correo y preferencias (120 sol/min)

### Endpoints Relacionados

* [Generar token JWT para plantillas](../api-reference/v01.15.00/jwt-management/generate-jwt-token-for-embedding-templates) - Para editor de plantillas embebido (120 sol/min)
* [Crear plantilla](../api-reference/v01.15.00/templates/create-template) - Crear plantillas por espacio de trabajo (120 sol/min)
* [Crear solicitud de firma](../api-reference/v01.15.00/signing-requests/create-signing-request) - Enviar documentos con marca del espacio de trabajo (120 sol/min)

***

## Próximos pasos

* [Crear espacios de trabajo](/guides/creating-workspaces) para aplicaciones multi-tenant
* [Enviar solicitudes de firma](/guides/sending-signing-request) con correos personalizados
* [Configurar webhooks](/guides/webhooks) para rastrear la actividad del espacio de trabajo
* [Editor de plantillas embebible](/guides/embeddable-template-editor) con autenticación JWT para integraciones embebidas


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.