# Cancelar y sustituir

> Cómo se cancela un CFDI ante el SAT, cuándo debe aceptarlo el cliente y cómo corregir una factura con otra que la sustituye.

Un CFDI timbrado no se edita: si tiene un error se cancela y, si hace falta, se emite otro que lo sustituye. La cancelación la pide Fiscalisto al SAT con el CSD del emisor.

## Cancelar

```bash tab="cURL"
curl https://api.fiscalisto.com/v1/invoices/inv_OufpKqnrvx9p0Nfbydl9jG4UNGeTYcvdrg/cancel \
  -H "Authorization: Bearer $FISCALISTO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "motive": "02" }'
```

```ts tab="TypeScript"
import { Fiscalisto } from "fiscalisto";

// La llave viene de FISCALISTO_API_KEY.
const fiscalisto = new Fiscalisto();

const invoice = await fiscalisto.invoices.cancel("inv_OufpKqnrvx9p0Nfbydl9jG4UNGeTYcvdrg", { motive: "02" });
```

```python tab="Python"
from fiscalisto import Fiscalisto

# La llave viene de FISCALISTO_API_KEY.
fiscalisto = Fiscalisto()

invoice = fiscalisto.invoices.cancel("inv_OufpKqnrvx9p0Nfbydl9jG4UNGeTYcvdrg", {"motive": "02"})
```

```php tab="PHP"
<?php

use Fiscalisto\Fiscalisto;

// La llave viene de FISCALISTO_API_KEY.
$fiscalisto = new Fiscalisto();

$invoice = $fiscalisto->invoices->cancel('inv_OufpKqnrvx9p0Nfbydl9jG4UNGeTYcvdrg', ['motive' => '02']);
```

```go tab="Go"
package main

import (
	"context"
	"fmt"
	"log"

	fiscalisto "fiscalisto.com/go"
)

func main() {
	// La llave viene de FISCALISTO_API_KEY.
	client, err := fiscalisto.NewClient("")
	if err != nil {
		log.Fatal(err)
	}
	ctx := context.Background()

	invoice, err := client.Invoices.Cancel(ctx, "inv_OufpKqnrvx9p0Nfbydl9jG4UNGeTYcvdrg", &fiscalisto.CancelInvoiceParams{
		Motive: fiscalisto.String("02"),
	})
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(invoice.ID)
}
```

```java tab="Java"
import com.fiscalisto.FiscalistoClient;
import com.fiscalisto.model.CancelInvoiceParams;

// La llave viene de FISCALISTO_API_KEY.
var fiscalisto = new FiscalistoClient();

var invoice = fiscalisto.invoices().cancel("inv_OufpKqnrvx9p0Nfbydl9jG4UNGeTYcvdrg", new CancelInvoiceParams().setMotive("02"));
System.out.println(invoice.getId());
```

```csharp tab="C#"
using Fiscalisto;

// La llave viene de FISCALISTO_API_KEY.
var fiscalisto = new FiscalistoClient();

var invoice = await fiscalisto.Invoices.CancelAsync("inv_OufpKqnrvx9p0Nfbydl9jG4UNGeTYcvdrg", new CancelInvoiceParams { Motive = "02" });
```

El SAT pide uno de cuatro motivos (`motive`):

| Motivo | Cuándo |
| --- | --- |
| `01` | Tiene errores y ya emitió la que la sustituye: mande `substitution` con su id o UUID. Mejor use [sustituir](#sustituir). |
| `02` | Tiene errores y no se va a sustituir. Es el que se usa si no manda `motive`. |
| `03` | La operación no se llevó a cabo. |
| `04` | Solo en una factura global: una venta incluida en ella se facturó después a nombre del cliente, así que se cancela la global (y se emite otra sin esa venta). En otra factura contesta `cancellation_failed`. |

## Cuándo debe aceptarlo el cliente

Si la factura pasa de $1,000 y no es de las excepciones del SAT, el cliente tiene que aceptar la cancelación en su buzón tributario. Mientras tanto la factura queda `status: "pending"`, con `cancellation.status: "pending"`, y sigue vigente. Después le llega uno de estos eventos:

- `invoice.canceled`: la aceptó, o no respondió en 3 días hábiles y el SAT la dio por aceptada.
- `invoice.cancellation_rejected`: la rechazó, o la solicitud venció. Puede pedirla otra vez.

Se cancelan en ese momento, sin aceptación, las facturas de hasta $1,000, las notas de crédito, los traslados, los recibos de nómina, las facturas a público en general o a un extranjero, y cualquier factura dentro de las 24 horas siguientes a su emisión. Las constancias de retenciones también se cancelan en ese momento.

Si pide la cancelación otra vez mientras espera al cliente, la API contesta el estado actual sin mandar otra solicitud. Si consulta la factura con `?refresh=true`, Fiscalisto pregunta al SAT su estado en ese momento.

## Lo que cuesta

Cancelar un comprobante no usa timbres, ni la primera solicitud ni las que repita después de un rechazo. El timbre que usó al timbrarlo no regresa.

## Sustituir

Para corregir una factura (otro precio, otro régimen del cliente, otro concepto), mande la factura corregida completa, con el mismo cuerpo que [`POST /v1/invoices`](https://fiscalisto.com/docs/api/referencia/facturas/create-invoice):

```bash tab="cURL"
curl https://api.fiscalisto.com/v1/invoices/inv_OufpKqnrvx9p0Nfbydl9jG4UNGeTYcvdrg/substitute \
  -H "Authorization: Bearer $FISCALISTO_API_KEY" \
  -H "Idempotency-Key: pedido-A-1042-corregida" \
  -H "Content-Type: application/json" \
  -d '{
    "customer": { "tax_id": "ICV060329BY0", "legal_name": "INMOBILIARIA CVA", "tax_system": "601", "zip": "33826" },
    "payment_form": "03",
    "items": [{ "description": "Mensualidad", "product_key": "86121700", "quantity": 2, "unit_price": 2400 }]
  }'
```

```ts tab="TypeScript"
import { Fiscalisto } from "fiscalisto";

// La llave viene de FISCALISTO_API_KEY.
const fiscalisto = new Fiscalisto();

const invoice = await fiscalisto.invoices.substitute("inv_OufpKqnrvx9p0Nfbydl9jG4UNGeTYcvdrg", {
  customer: {
    tax_id: "ICV060329BY0",
    legal_name: "INMOBILIARIA CVA",
    tax_system: "601",
    zip: "33826",
  },
  payment_form: "03",
  items: [
    {
      description: "Mensualidad",
      product_key: "86121700",
      quantity: 2,
      unit_price: 2400,
    },
  ],
}, { idempotencyKey: "pedido-A-1042-corregida" });
```

```python tab="Python"
from fiscalisto import Fiscalisto

# La llave viene de FISCALISTO_API_KEY.
fiscalisto = Fiscalisto()

invoice = fiscalisto.invoices.substitute("inv_OufpKqnrvx9p0Nfbydl9jG4UNGeTYcvdrg", {
    "customer": {
        "tax_id": "ICV060329BY0",
        "legal_name": "INMOBILIARIA CVA",
        "tax_system": "601",
        "zip": "33826",
    },
    "payment_form": "03",
    "items": [
        {
            "description": "Mensualidad",
            "product_key": "86121700",
            "quantity": 2,
            "unit_price": 2400,
        },
    ],
}, idempotency_key="pedido-A-1042-corregida")
```

```php tab="PHP"
<?php

use Fiscalisto\Fiscalisto;

// La llave viene de FISCALISTO_API_KEY.
$fiscalisto = new Fiscalisto();

$invoice = $fiscalisto->invoices->substitute('inv_OufpKqnrvx9p0Nfbydl9jG4UNGeTYcvdrg', [
    'customer' => [
        'tax_id' => 'ICV060329BY0',
        'legal_name' => 'INMOBILIARIA CVA',
        'tax_system' => '601',
        'zip' => '33826',
    ],
    'payment_form' => '03',
    'items' => [
        [
            'description' => 'Mensualidad',
            'product_key' => '86121700',
            'quantity' => 2,
            'unit_price' => 2400,
        ],
    ],
], ['idempotency_key' => 'pedido-A-1042-corregida']);
```

```go tab="Go"
package main

import (
	"context"
	"fmt"
	"log"

	fiscalisto "fiscalisto.com/go"
)

func main() {
	// La llave viene de FISCALISTO_API_KEY.
	client, err := fiscalisto.NewClient("")
	if err != nil {
		log.Fatal(err)
	}
	ctx := context.Background()

	invoice, err := client.Invoices.Substitute(ctx, "inv_OufpKqnrvx9p0Nfbydl9jG4UNGeTYcvdrg", &fiscalisto.InvoiceCreate{
		Customer: fiscalisto.ObjectOf(fiscalisto.InvoiceCreateCustomer{
			TaxID:     "ICV060329BY0",
			LegalName: "INMOBILIARIA CVA",
			TaxSystem: "601",
			Zip:       "33826",
		}),
		PaymentForm: fiscalisto.String("03"),
		Items: []fiscalisto.InvoiceCreateItem{
			{
				Description: fiscalisto.String("Mensualidad"),
				ProductKey:  fiscalisto.String("86121700"),
				Quantity:    fiscalisto.Float64(2),
				UnitPrice:   fiscalisto.Float64(2400),
			},
		},
	}, fiscalisto.WithIdempotencyKey("pedido-A-1042-corregida"))
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(invoice.ID)
}
```

```java tab="Java"
import com.fiscalisto.FiscalistoClient;
import com.fiscalisto.RequestOptions;
import com.fiscalisto.model.InvoiceCreate;
import com.fiscalisto.model.InvoiceCreateCustomer;
import com.fiscalisto.model.InvoiceCreateItem;
import java.util.List;

// La llave viene de FISCALISTO_API_KEY.
var fiscalisto = new FiscalistoClient();

var invoice = fiscalisto.invoices().substitute("inv_OufpKqnrvx9p0Nfbydl9jG4UNGeTYcvdrg", new InvoiceCreate()
        .setCustomer(new InvoiceCreateCustomer()
                .setTaxId("ICV060329BY0")
                .setLegalName("INMOBILIARIA CVA")
                .setTaxSystem("601")
                .setZip("33826"))
        .setPaymentForm("03")
        .setItems(List.of(
                new InvoiceCreateItem()
                        .setDescription("Mensualidad")
                        .setProductKey("86121700")
                        .setQuantity(2)
                        .setUnitPrice(2400)
        )), RequestOptions.idempotencyKey("pedido-A-1042-corregida"));
System.out.println(invoice.getId());
```

```csharp tab="C#"
using Fiscalisto;

// La llave viene de FISCALISTO_API_KEY.
var fiscalisto = new FiscalistoClient();

var invoice = await fiscalisto.Invoices.SubstituteAsync("inv_OufpKqnrvx9p0Nfbydl9jG4UNGeTYcvdrg", new InvoiceCreate
{
    Customer = new InvoiceCreateCustomer
    {
        TaxId = "ICV060329BY0",
        LegalName = "INMOBILIARIA CVA",
        TaxSystem = "601",
        Zip = "33826",
    },
    PaymentForm = "03",
    Items = new()
    {
        new()
        {
            Description = "Mensualidad",
            ProductKey = "86121700",
            Quantity = 2,
            UnitPrice = 2400,
        },
    },
}, new RequestOptions { IdempotencyKey = "pedido-A-1042-corregida" });
```

Fiscalisto timbra la nueva relacionada a la anterior (relación `04`) y cancela la anterior con el motivo `01`, en una sola llamada. La respuesta es la factura nueva; su `substitutes` dice cuál sustituyó y si ya quedó cancelada. Si no se pudo cancelar, `substitutes.cancel_error` dice por qué; corríjalo y cancélela con [`POST /v1/invoices/{id}/cancel`](https://fiscalisto.com/docs/api/referencia/facturas/cancel-invoice) con el motivo `01` y la nueva en `substitution`. Si la anterior necesita aceptación del cliente, la nueva ya está timbrada y la anterior queda `pending` hasta que responda.

Los complementos de pago y las notas de crédito no se sustituyen: se cancelan y se emite otro.
