Saltar al contenido principal

Nota de Crédito (05)

La Nota de Crédito (tipo 05) se emite para ajustar un CCF (tipo 03) ya emitido. Se usa típicamente para:

  • Devoluciones de mercadería.
  • Correcciones de precio o cantidad posteriores a la emisión.
  • Anulación de un CCF que ya salió de la ventana de 3 meses donde la invalidación directa todavía es posible.
  • Descuentos aplicados después de la facturación.

La NC no aplica a Facturas (01), Exportaciones (11), ni SUEX (14). Para esos tipos se usa Anulación directa.

/credit-memo vs /invalidate — cuándo usar cada uno

Cuando necesitas revertir un CCF tienes dos caminos:

SituaciónEndpointQué hace
Ajuste parcial (p.ej. devolución de 2 items de 10)POST /credit-memo con linesGenera NC parcial, deja el CCF vigente.
Ajuste total intencionalPOST /credit-memo con full_adjustment: trueGenera NC completa, marca el CCF como invalidado.
Anular CCF completo con menos de 3 mesesPOST /invalidate/{id}Anulación directa en MH. Más simple.
Anular CCF completo con más de 3 mesesPOST /invalidate/{id}El API crea automáticamente una NC porque el MH ya no permite anulación directa.

Si solo quieres anular un CCF completo, usa /invalidate — el API decide solo si hacer anulación directa o generar una NC automática. Usa /credit-memo cuando necesitas control explícito sobre las líneas ajustadas.

Detalles técnicos

MétodoPOST
URLhttps://ocote.io/api/connect/credit-memo
Content-Typeapplication/json
AutenticaciónHeader Authorization: Bearer odt_...

Request

Campos

CampoTipoRequeridoDescripción
document_idstringUUID o external_ref del CCF a ajustar.
full_adjustmentboolNo (default false)Si true, genera NC completa (clona todas las líneas del CCF) e invalida el CCF.
linesarrayNoLíneas específicas a ajustar. Solo válido con full_adjustment: false. Si no envías líneas y full_adjustment: false, el API clona todas las del CCF pero no invalida.
external_refstringNoTu identificador idempotente para la NC.

Líneas (lines)

CampoTipoRequeridoDescripción
descriptionstringDescripción del ítem ajustado (típicamente igual al del CCF original).
quantityfloatCantidad a ajustar.
unit_pricefloatPrecio unitario con IVA incluido.
discount_percentagefloatNo (default 0)Descuento.

Los tres modos de emisión

Modo 1: Ajuste completo explícito (full_adjustment: true)

El API copia todas las líneas del CCF original a la NC y marca el CCF como invalidado. Equivale a una anulación pero genera un documento fiscal explícito de ajuste.

{
"document_id": "CCF-ENLACES-001",
"full_adjustment": true,
"external_ref": "NC-DEV-TOTAL-001"
}

Úsalo cuando quieres un documento de anulación formal con todas las líneas visibles.

Modo 2: Ajuste parcial (full_adjustment: false + lines)

Solo ajustas lo que especificas en lines. El CCF queda vigente pero con monto ajustado.

{
"document_id": "CCF-ENLACES-001",
"full_adjustment": false,
"lines": [
{
"description": "Laptop Dell XPS devuelta",
"quantity": 1,
"unit_price": 1200.00
}
],
"external_ref": "NC-DEV-PARCIAL-001"
}

Úsalo para devoluciones reales (regresó 1 item de 10) o descuentos post-facturación.

Modo 3: Clonación sin invalidar (sin lines, full_adjustment: false)

El API clona todas las líneas del CCF pero no lo invalida. Es útil para "anotar" un ajuste completo sin anular fiscalmente el CCF.

{
"document_id": "CCF-ENLACES-001",
"external_ref": "NC-AJUSTE-001"
}

Es el modo menos común. Si necesitas anular completamente, prefiere Modo 1 o el endpoint /invalidate.

Validaciones

El API valida sobre el CCF antes de procesar:

ValidaciónHTTPMensaje
CCF no existe o no es tuyo404Documento no encontrado
Documento referenciado no es CCF400Solo se pueden emitir Notas de Credito sobre CCF (tipo 03)
CCF ya invalidado400Este CCF ya fue invalidado
CCF ya completamente ajustado400Este CCF ya fue completamente ajustado
CCF con más de 90 días400Este CCF tiene mas de 90 dias y no puede ser ajustado
Ventana de 90 días

La NC directa vía /credit-memo solo aplica mientras el CCF tiene menos de 90 días. Después de eso, el API rechaza el ajuste y debes usar otros mecanismos contables. Si tu intención era simplemente anular un CCF antiguo completo, usa POST /invalidate/{ccf_id} — ese endpoint maneja automáticamente el caso de fuera de ventana generando la NC equivalente.

Response

Además de los flags estándar (Respuestas y estados), la NC devuelve campos específicos:

CampoTipoDescripción
document_idstring (UUID)ID de la NC creada.
external_refstringEl que enviaste (o autogenerado).
control_numberstringCorrelativo de la NC (DTE-05-...).
generation_codestringCódigo de generación MH.
reception_stampstringSello MH de la NC.
ccf_control_numberstringCorrelativo del CCF ajustado.
ccf_adjusted_amountfloatMonto total ajustado (suma de líneas de la NC).
ccf_fully_adjustedbooltrue si el CCF quedó completamente ajustado tras esta NC.
dteobjectSchema firmado completo de la NC (solo si dte_success: true).

Ejemplo: devolución parcial

Escenario: emitiste un CCF por tres productos ($500, $300, $200 = $1 000 gravable). El cliente devuelve solo el producto de $300. Emites NC parcial:

curl
curl -X POST https://ocote.io/api/connect/credit-memo \
-H "Authorization: Bearer odt_xxx" \
-H "Content-Type: application/json" \
-d '{
"document_id": "CCF-ENLACES-TEST-001",
"external_ref": "NC-ENLACES-TEST-001",
"full_adjustment": false,
"lines": [
{
"description": "Silla ergonómica de oficina",
"quantity": 1,
"unit_price": 339.00
}
]
}'
Node.js (axios)
const { data } = await axios.post(
'https://ocote.io/api/connect/credit-memo',
{
document_id: 'CCF-ENLACES-TEST-001',
external_ref: 'NC-ENLACES-TEST-001',
full_adjustment: false,
lines: [
{ description: 'Silla ergonómica de oficina',
quantity: 1, unit_price: 339.00 },
],
},
{ headers: { Authorization: `Bearer ${process.env.OCOTE_API_KEY}` } }
);
Python (requests)
r = requests.post(
"https://ocote.io/api/connect/credit-memo",
headers={"Authorization": f"Bearer {API_KEY}"},
json={
"document_id": "CCF-ENLACES-TEST-001",
"external_ref": "NC-ENLACES-TEST-001",
"full_adjustment": False,
"lines": [
{
"description": "Silla ergonómica de oficina",
"quantity": 1,
"unit_price": 339.00,
},
],
},
)

Respuesta exitosa

{
"success": true,
"dte_success": true,
"contingency": false,
"rejected": false,
"posted": true,
"is_duplicate": false,

"document_id": "63e82935-9d98-43db-b2b8-bc669364885c",
"external_ref": "NC-ENLACES-TEST-001",
"control_number": "DTE-05-M001P002-000000000000001",
"generation_code": "63E82935-9D98-43DB-B2B8-BC669364885C",
"reception_stamp": "20260421145823...",

"ccf_control_number": "DTE-03-M001P002-000000000000008",
"ccf_adjusted_amount": 339.00,
"ccf_fully_adjusted": false,

"observaciones": [],
"dte_state": "",
"dte_message": ""
}

Observa:

  • ccf_control_number apunta al CCF ajustado — útil para tu conciliación.
  • ccf_adjusted_amount: 339.00 es el monto que esta NC descuenta del CCF.
  • ccf_fully_adjusted: false indica que el CCF aún tiene monto vigente — podrías emitir más NC si siguen llegando devoluciones, siempre dentro de los 90 días.

Ajuste total

Para anular un CCF completo vía NC (dentro de ventana):

curl
curl -X POST https://ocote.io/api/connect/credit-memo \
-H "Authorization: Bearer odt_xxx" \
-H "Content-Type: application/json" \
-d '{
"document_id": "CCF-ENLACES-TEST-002",
"full_adjustment": true,
"external_ref": "NC-ANULA-CCF-002"
}'

Este modo es equivalente a lo que hace POST /invalidate/{id} internamente cuando el CCF está fuera de ventana. La diferencia es que aquí tú tomas la decisión explícita.

Idempotencia y retry

Aplican las mismas reglas de Retry e idempotencia:

  • Mismo external_ref devuelto con éxito → is_duplicate: true, la NC original.
  • Mismo external_ref que falló previamente → reutiliza registro con el correlativo original, actualizando datos del payload nuevo.
  • El document_id del CCF no se puede cambiar en retry. El API ignora document_id del payload nuevo y conserva el CCF vinculado originalmente. Si quieres emitir NC sobre otro CCF, usa otro external_ref.

Ver también