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

# Sincronizar cuentas de una cartera

> Sincroniza por lotes filas de cuentas en una cartera con los modos APPEND_ONLY, UPDATE_EXISTING y REPLACE.

Carga y mantén las cuentas de deuda de una cartera desde tu propio sistema de origen con
`client.portfolios.syncAccounts`. Envías un lote de filas de cuentas y un modo de fusión;
QCobro empareja cada fila con una cuenta existente por su `externalId` y aplica el modo que
elegiste.

## Requisitos previos

<Note>
  `syncAccounts` está acotado a un workspace. Necesitas un [`Client`](/sdk/authentication)
  autenticado con un workspace activo seleccionado, y el `id` de una cartera donde
  sincronizar. Crea una antes con [`client.portfolios.create`](/sdk/portfolios).
</Note>

## Sincroniza un lote

<Steps>
  <Step title="Autentícate y selecciona un workspace">
    Construye un cliente, inicia sesión y elige el workspace al que pertenece la cartera.

    ```ts theme={null}
    import { Client } from "@qcobro/sdk";

    const client = new Client();
    await client.login({ email: "me@acme.com", password: process.env.QCOBRO_PASSWORD! });
    client.useWorkspace("ws_abc123");
    ```
  </Step>

  <Step title="Envía las filas">
    Pasa el `portfolioId`, un `mode` y al menos una fila. Cada fila necesita un `externalId`,
    un `fullName` y un `outstandingBalance`.

    ```ts theme={null}
    const result = await client.portfolios.syncAccounts({
      portfolioId: "ptf_123",
      mode: "APPEND_ONLY",
      rows: [
        { externalId: "A-1", fullName: "Jane Doe", outstandingBalance: 1200.5 },
        { externalId: "A-2", fullName: "John Roe", outstandingBalance: 845 }
      ]
    });
    ```
  </Step>

  <Step title="Lee los conteos">
    La llamada devuelve cuántas cuentas se crearon, actualizaron y archivaron, y el nuevo
    total activo de la cartera.

    ```ts theme={null}
    console.log(result); // { created: 2, updated: 0, archived: 0, total: 2 }
    ```
  </Step>
</Steps>

## Elige un modo de sincronización

El `mode` decide qué pasa con las cuentas existentes. Las filas se emparejan con las cuentas
por `externalId` dentro de la cartera.

| Modo              | Fila con un `externalId` nuevo | Fila que coincide con una cuenta existente | Cuenta existente **ausente** del lote |
| ----------------- | ------------------------------ | ------------------------------------------ | ------------------------------------- |
| `APPEND_ONLY`     | Se crea                        | Se deja sin cambios                        | Se deja sin cambios                   |
| `UPDATE_EXISTING` | Se crea                        | Se actualiza                               | Se deja sin cambios                   |
| `REPLACE`         | Se crea                        | Se actualiza                               | Se archiva                            |

<Tabs>
  <Tab title="APPEND_ONLY">
    Añade cuentas nuevas y nunca toca las que ya existen. Úsalo para cargas incrementales en
    las que los datos previos son la fuente autoritativa.

    ```ts theme={null}
    await client.portfolios.syncAccounts({ portfolioId: "ptf_123", mode: "APPEND_ONLY", rows });
    ```
  </Tab>

  <Tab title="UPDATE_EXISTING">
    Añade cuentas nuevas y refresca las que ya existen —un upsert. Úsalo para feeds
    recurrentes que traen saldos o datos de contacto actualizados.

    ```ts theme={null}
    await client.portfolios.syncAccounts({ portfolioId: "ptf_123", mode: "UPDATE_EXISTING", rows });
    ```
  </Tab>

  <Tab title="REPLACE">
    Haz que la cartera refleje el lote: crea, actualiza y archiva cualquier cuenta cuyo
    `externalId` no esté en `rows`. Úsalo cuando el lote es una instantánea completa de la
    cartera.

    ```ts theme={null}
    await client.portfolios.syncAccounts({ portfolioId: "ptf_123", mode: "REPLACE", rows });
    ```
  </Tab>
</Tabs>

<Warning>
  `REPLACE` **archiva** toda cuenta existente cuyo `externalId` falte en el lote, y expira las
  promesas de pago pendientes de esas cuentas. Envía siempre una instantánea completa con
  `REPLACE` — un lote parcial archivará las cuentas que dejaste fuera.
</Warning>

## Da forma a las filas de cuenta

Cada entrada de `rows` se valida contra el esquema de cuenta compartido. Solo tres campos son
obligatorios; el resto es opcional, y los campos numéricos de abajo toman `0` por defecto
cuando se omiten.

<ParamField body="externalId" type="string" required>
  Tu identificador estable de la cuenta. Las cuentas se emparejan entre sincronizaciones por
  este valor, así que debe ser único dentro de la cartera.
</ParamField>

<ParamField body="fullName" type="string" required>
  Nombre completo del titular de la cuenta.
</ParamField>

<ParamField body="outstandingBalance" type="number" required>
  Importe adeudado actual. Debe ser cero o mayor.
</ParamField>

<ParamField body="phone" type="string">
  Teléfono de contacto usado para gestiones por voz y SMS.
</ParamField>

<ParamField body="email" type="string">
  Email de contacto. Debe ser una dirección válida cuando se aporta.
</ParamField>

<ParamField body="preferredLanguage" type="string">
  Idioma preferido para las gestiones (QCobro es multilingüe).
</ParamField>

<ParamField body="bestTimeToCall" type="string">
  Nota libre sobre cuándo prefiere ser contactado el titular.
</ParamField>

<ParamField body="customerSegment" type="string">
  Tu propia etiqueta de segmentación de la cuenta.
</ParamField>

<ParamField body="principalAmount" type="number" default="0">
  Principal original adeudado.
</ParamField>

<ParamField body="termsAmount" type="number" default="0">
  Importe de la cuota según las condiciones de la cuenta.
</ParamField>

<ParamField body="termsFrequency" type="string">
  Cada cuánto vencen las cuotas (por ejemplo `monthly`).
</ParamField>

<ParamField body="termsLength" type="number" default="0">
  Número de cuotas del plazo.
</ParamField>

<ParamField body="daysPastDue" type="number" default="0">
  Días de mora de la cuenta.
</ParamField>

<ParamField body="missedInstallments" type="number" default="0">
  Número de cuotas impagadas.
</ParamField>

<ParamField body="lastPaymentDate" type="string">
  Fecha del último pago, como cadena de fecha ISO.
</ParamField>

<ParamField body="lastPaymentAmount" type="number">
  Importe del último pago.
</ParamField>

<ParamField body="negotiationOptions" type="string">
  Notas sobre opciones de negociación o acuerdo aceptables para esta cuenta.
</ParamField>

## Lee el resultado

`syncAccounts` resuelve a un resumen de lo que cambió:

<ResponseField name="created" type="number">
  Cuentas insertadas porque su `externalId` era nuevo para la cartera.
</ResponseField>

<ResponseField name="updated" type="number">
  Cuentas existentes refrescadas. Siempre `0` en `APPEND_ONLY`.
</ResponseField>

<ResponseField name="archived" type="number">
  Cuentas archivadas por estar ausentes del lote. Distinto de cero solo en `REPLACE`.
</ResponseField>

<ResponseField name="total" type="number">
  Número de cuentas activas de la cartera tras la sincronización (las archivadas se excluyen).
</ResponseField>

## Maneja los errores de validación

Las filas se validan **en el cliente** contra el esquema compartido de QCobro antes de enviar
cualquier petición. Un lote vacío, un `mode` desconocido o un `email` malformado lanza un
`ValidationError` con detalle por campo y no hace ninguna llamada de red.

```ts theme={null}
import { ValidationError } from "@qcobro/sdk";

try {
  await client.portfolios.syncAccounts({
    portfolioId: "ptf_123",
    mode: "APPEND_ONLY",
    rows: [] // los lotes vacíos se rechazan
  });
} catch (err) {
  if (err instanceof ValidationError) {
    console.error(err.fieldErrors); // [{ field: "rows", message: "...", code: "..." }]
  }
}
```

Consulta [Validación y errores](/sdk/errors) para la forma completa del error.

## Siguientes pasos

<CardGroup cols={2}>
  <Card title="Gestionar carteras" icon="folder-open" href="/sdk/portfolios">
    Crea, actualiza, archiva y lista las carteras donde sincronizas.
  </Card>

  <Card title="Validación y errores" icon="triangle-exclamation" href="/sdk/errors">
    Captura los fallos de validación del cliente y lee el detalle por campo.
  </Card>
</CardGroup>
