Cancelación de reembolsos
Cómo se previsualiza y se cancela un reembolso de factura desde el panel o por MCP (no está en la API externa) y qué contiene el aviso INVOICE_REFUND_CANCELLED.
Actualizado el . 6 min de lectura
Incluido en todos los planes. Ver precios
Cancelar un reembolso deshace una devolución ya registrada en UTILIA OS: restaura el saldo de la factura, corrige comisiones, contabilidad, gamificación y CRM, y puede cancelar la factura rectificativa en borrador vinculada. Como mueve dinero y asientos, se hace en dos pasos:
Cancelar un reembolso
GET …/cancel-preview
Vista previa
Muestra todo lo que cambiaría, sin aplicar nada.Si no: cancellable: false y blockReason explica el motivo.
POST …/cancel
Cancelación
Con confirm: true, cuando la persona confirma.Si no: 409 si hay una disputa abierta en Stripe.
Tu servidor
Aviso INVOICE_REFUND_CANCELLED
Lo reciben una vez las aplicaciones suscritas.
No forma parte de la API externa
Estos endpoints están bajo /api/finance/invoices/..., no bajo
/api/external/v1/, y exigen la sesión de un usuario del panel
(Authorization: Bearer <token>) con el permiso INVOICES_REFUND_CANCEL.
Una aplicación externa con X-Api-Key recibe 401, y los SDK no los
alcanzan.
Lo que sí puede hacer una aplicación externa es emitir un
reembolso, con POST /external/v1/invoices/:id/refund.
Deshacerlo después lo decide tu equipo, desde Finanzas o con la
herramienta MCP.
Reglas de la operación
- La cancelación es siempre total. Para devolver solo una parte, registra otro reembolso por la diferencia.
- El cuerpo exige
confirm: truecomo valor booleano. Cualquier otro valor (también"true"como texto) devuelve400. - Si el reembolso era de Stripe y estaba pendiente (
PENDING), se cancela también en Stripe. En el resto de casos (manuales, o de Stripe ya completados) se cancela solo en UTILIA OS. - Si hay una disputa abierta en Stripe sobre el cobro, la operación se bloquea con
409: la disputa la resuelve Stripe. - Si el reembolso generó una rectificativa en borrador, se cancela también por defecto (
alsoCancelRectifyingInvoice: true). Si la rectificativa ya está emitida y firmada con VeriFactu, no se toca y la vista previa lo avisa. - Si el reembolso ya estaba cancelado, la respuesta es
200conalreadyCancelled: truey no se repite ningún efecto. - Límites: 20 peticiones por minuto y usuario en la vista previa, y 5 en la cancelación.
Previsualizar la cancelación
/api/finance/invoices/:invoiceId/refunds/:refundId/cancel-previewDevuelve todo lo que cambiaría al cancelar un reembolso, sin aplicar nada. Se puede repetir sin efectos.
Parámetros de ruta
- invoiceIdstringobligatorio
- Identificador de la factura en UTILIA OS.
- refundIdstringobligatorio
- Identificador del reembolso que quieres evaluar.
Respuesta
Devuelve la factura, el reembolso, los avisos y los efectos que tendría la cancelación. Si cancellable es false, blockReason explica el motivo en castellano.
{
"refund": {
"id": "clx987zyx",
"source": "STRIPE",
"status": "PENDING",
"amount": 25.5,
"currency": "EUR",
"createdAt": "2026-04-19T10:14:22.000Z",
"initiatedBy": { "fullName": "Marta López" }
},
"invoice": {
"id": "clx111aaa",
"invoiceNumber": "202600042",
"status": "PARTIALLY_PAID",
"paidAmount": 224.5,
"totalAmount": 250.0
},
"cancellable": true,
"blockReason": null,
"warnings": [
"El reembolso se emitió hace 32 días; revisa que el cliente no haya solicitado nada en el chat antes de cancelar."
],
"effects": {
"invoice": { "restorePaidAmount": true, "newPaidAmount": 250.0, "newStatus": "PAID", "reason": null },
"stripe": { "action": "cancel-on-stripe", "reason": "Se invocará stripe.refunds.cancel sobre re_3OabcDEF porque está en estado PENDING." },
"commission": { "affected": [{ "commissionId": "clx-c-1", "agentName": "Marta López", "kind": "restore", "amount": 12.5, "alreadySettled": false }] },
"accounting": {
"newJournalEntry": {
"type": "REFUND_CANCELLATION",
"period": "2026-05",
"totalDebit": 25.5,
"totalCredit": 25.5,
"accounts": [{ "code": "430", "debit": 25.5, "credit": 0 }, { "code": "572", "debit": 0, "credit": 25.5 }]
}
},
"gamification": { "pointsRestored": 50, "affectedUsers": [{ "userId": "u-1", "fullName": "Marta López", "points": 50 }] },
"crm": { "interactionWillBeCreated": true, "clientName": "Empresa Receptora SL" },
"customerEmail": { "willBeSent": true, "reason": "Se notificará al cliente porque recibió el justificante del reembolso.", "recipients": ["cliente@empresa.com"] },
"webhook": { "willBeDispatched": true, "externalApps": ["mi-app-contable"] },
"rectifyingInvoice": { "exists": true, "id": "clx-r-1", "number": "R-202600005", "status": "DRAFT", "action": "cancel", "verifactuSigned": false, "warning": null }
}
}Códigos de respuesta
- 200OK
- Vista previa generada (también cuando
cancellableesfalse). - 403Forbidden
- El usuario no tiene
INVOICES_REFUND_CANCELo la factura queda fuera de su alcance. - 404Not Found
- Factura o reembolso no encontrados.
Ejecutar la cancelación
/api/finance/invoices/:invoiceId/refunds/:refundId/cancelCancela un reembolso ya emitido. Si ya estaba cancelado, devuelve alreadyCancelled: true sin repetir efectos.
Cuerpo de la petición
- cancellationReasonstringobligatorio
- Motivo concreto (de 10 a 500 caracteres, sin contar espacios). Queda en el registro de auditoría, en el aviso, en la interacción del CRM y, si no envías cancellationCustomerMessage, en el correo al cliente.
- cancellationNotesstring
- Notas internas para el equipo de finanzas (máximo 2.000 caracteres). Nunca se envían al cliente.
- cancellationCustomerMessagestring
- Mensaje para el correo al cliente (máximo 500 caracteres). Si lo omites, se usa cancellationReason.
- alsoCancelRectifyingInvoiceboolean
- Por defecto, true. Si la rectificativa vinculada ya está emitida y firmada con VeriFactu, se ignora y la respuesta lo avisa.
- confirmbooleanobligatorio
- Tiene que ser exactamente
true. Evita cancelaciones por accidente.
Respuesta
{
"refundId": "clx987zyx",
"status": "CANCELLED",
"alreadyCancelled": false,
"invoiceRestored": true,
"newInvoiceStatus": "PAID",
"newPaidAmount": 250.0,
"stripeCancelled": true,
"rectifyingInvoiceCancelled": true,
"cancelledAt": "2026-05-08T14:32:11.000Z"
}Códigos de respuesta
- 200OK
- Cancelado correctamente o respuesta idempotente cuando ya estaba cancelado.
- 400Bad Request
- Cuerpo no válido: falta
confirm: trueocancellationReasonestá vacío o es demasiado corto. - 403Forbidden
- Sin permiso
INVOICES_REFUND_CANCELo fuera del alcance del usuario. - 404Not Found
- Factura o reembolso no encontrados.
- 409Conflict
- No se puede cancelar: hay una disputa abierta en Stripe, otra cancelación simultánea, la factura no cuadra o la rectificativa no se puede tocar.
- 502Bad Gateway
- Stripe rechazó la cancelación. El reembolso queda como estaba; vuelve a intentarlo en unos segundos.
Desde el producto
En la ficha de la factura, con la acción de cancelar el reembolso. El diálogo muestra primero el alcance de la cancelación y solo aplica los cambios cuando la persona confirma.
Los SDK no ofrecen esta operación: invoices.refunds.cancelPreview e
invoices.refunds.cancel atacaban esta ruta de la API del panel, devolvían siempre
401 y se retiraron en la versión 3.0.0. Lo que sí ofrecen es
invoices.refunds.create, que emite el reembolso por la API externa.
Herramienta MCP
Un asistente de IA con el permiso INVOICES_REFUND_CANCEL puede
hacerlo con la herramienta crm_invoices_workflow del servidor MCP de
finanzas: cancel_refund_preview para la vista previa y cancelRefund
para cancelar.
{
"tool": "crm_invoices_workflow",
"operation": "cancelRefund",
"invoiceId": "inv-uuid",
"refundId": "refund-uuid",
"cancellationReason": "Reembolso emitido por error en la conciliación bancaria del 1 de mayo de 2026.",
"cancellationNotes": "Confirmado por Marta tras revisar el ticket SUP-1234.",
"cancellationCustomerMessage": "Hemos detectado que el reembolso anterior se emitió por error. Tu factura vuelve a estar pagada y no necesitas hacer nada.",
"alsoCancelRectifyingInvoice": true
}En MCP no se envía confirm: llamar a la herramienta ya cuenta como
confirmación de la persona que la usa.
Aviso INVOICE_REFUND_CANCELLED
Al completar la cancelación, las aplicaciones suscritas reciben el
aviso INVOICE_REFUND_CANCELLED, una sola vez por cancelación (no se
repite en las respuestas con alreadyCancelled: true). Su contenido:
- invoiceIdstring
- Identificador de la factura en UTILIA OS.
- invoiceNumberstring
- Número fiscal de la factura.
- refundIdstring
- Identificador del reembolso cancelado en UTILIA OS.
- stripeRefundIdstring | null
- Identificador del reembolso en Stripe (
re_xxx). Nulo si era manual. - sourceSTRIPE | MANUAL_BANK | MANUAL_CASH | MANUAL_OTHER
- Origen del reembolso original.
- originalAmountnumber
- Importe del reembolso cancelado, en euros (dos decimales).
- originalAmountCentsinteger
- Mismo importe en céntimos.
- currencystring
- Divisa (código ISO 4217 en mayúsculas).
- originalReasonstring | null
- Motivo del reembolso original, con los valores de Stripe (
requested_by_customer…). - wasFullRefundboolean
- true si el reembolso original devolvió todo lo cobrado.
- cancelledByIdstring
- Identificador del usuario que ejecuta la cancelación.
- cancelledAtstring
- Momento de la cancelación (ISO 8601, UTC).
- cancellationReasonstring
- Motivo de la cancelación.
- cancellationNotesstring | null
- Notas internas (no se envían al cliente).
- cancellationCustomerMessagestring | null
- Mensaje específico al cliente.
- invoiceRestoredboolean
- true si la cancelación restauró saldo a la factura.
- previousInvoiceStatusInvoiceStatus
- Estado de la factura ANTES de aplicar la cancelación.
- newInvoiceStatusInvoiceStatus
- Estado de la factura DESPUÉS de aplicar la cancelación.
- stripeCancelledboolean
- true si se invocó
stripe.refunds.cancel. - rectifyingInvoiceIdstring | null
- Identificador de la rectificativa vinculada (nulo si no había).
- rectifyingInvoiceWasCancelledboolean
- true si también se canceló la rectificativa en borrador.
- paymentInitiatorPaymentInitiator | null
- Copia de los datos de quien pagó originalmente.
- paymentInitiatorTypestring | null
- Tipo del actor pagador (USER, PORTAL_USER, EXTERNAL_USER, SYSTEM).
- paymentInitiatorIdstring | null
- Identificador del actor pagador.
Además del aviso, la cancelación revierte las comisiones (o genera un cargo si ya estaban liquidadas), crea un asiento contable de signo contrario sin borrar el original, restaura los puntos de gamificación, añade una interacción en la ficha del cliente en el CRM, avisa al cliente por correo cuando procede y queda en el registro de auditoría.