# Autofactura

> Registre cada venta como ticket y deje que su comprador se facture solo, en su página o en el portal del emisor; lo que nadie factura entra en la factura global.

Con la autofactura usted no le pide datos fiscales a cada comprador. Registra la venta como **ticket**, le da al comprador su código (impreso en el ticket, en el correo de la compra o en su página) y él se factura solo: escribe el código y el total, da sus datos fiscales (escritos o leídos de su constancia) y la factura se timbra en ese momento con los timbres de su cuenta, con su PDF y su XML para descargar.

Lo que nadie factura dentro del plazo entra solo en la **factura global** del periodo (público en general), timbrada la mañana después de que termina.

## Registrar una venta

```bash tab="cURL"
curl https://api.fiscalisto.com/v1/tickets \
  -H "Authorization: Bearer $FISCALISTO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "folio": "A1042",
    "total": 580,
    "payment_form": "04",
    "product_key": "90101501",
    "description": "Consumo de alimentos",
    "sold_at": "2026-10-24T14:32:00-06:00"
  }'
```

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

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

const ticket = await fiscalisto.tickets.create({
  folio: "A1042",
  total: 580,
  payment_form: "04",
  product_key: "90101501",
  description: "Consumo de alimentos",
  sold_at: "2026-10-24T14:32:00-06:00",
});
```

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

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

ticket = fiscalisto.tickets.create({
    "folio": "A1042",
    "total": 580,
    "payment_form": "04",
    "product_key": "90101501",
    "description": "Consumo de alimentos",
    "sold_at": "2026-10-24T14:32:00-06:00",
})
```

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

use Fiscalisto\Fiscalisto;

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

$ticket = $fiscalisto->tickets->create([
    'folio' => 'A1042',
    'total' => 580,
    'payment_form' => '04',
    'product_key' => '90101501',
    'description' => 'Consumo de alimentos',
    'sold_at' => '2026-10-24T14:32:00-06:00',
]);
```

```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()

	ticket, err := client.Tickets.Create(ctx, &fiscalisto.CreateTicketParams{
		Folio:       "A1042",
		Total:       580,
		PaymentForm: "04",
		ProductKey:  fiscalisto.String("90101501"),
		Description: fiscalisto.String("Consumo de alimentos"),
		SoldAt:      fiscalisto.String("2026-10-24T14:32:00-06:00"),
	})
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(ticket.ID)
}
```

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

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

var ticket = fiscalisto.tickets().create(new CreateTicketParams()
        .setFolio("A1042")
        .setTotal(580)
        .setPaymentForm("04")
        .setProductKey("90101501")
        .setDescription("Consumo de alimentos")
        .setSoldAt("2026-10-24T14:32:00-06:00"));
System.out.println(ticket.getId());
```

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

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

var ticket = await fiscalisto.Tickets.CreateAsync(new CreateTicketParams
{
    Folio = "A1042",
    Total = 580,
    PaymentForm = "04",
    ProductKey = "90101501",
    Description = "Consumo de alimentos",
    SoldAt = "2026-10-24T14:32:00-06:00",
});
```

El concepto viene de `product_key` y `description` (con `iva`, 0.16 por omisión) o de un producto de su catálogo con `product: "prd_…"`. El `total` lleva los impuestos incluidos; la propina va aparte en `tip` y no entra en la factura.

Contesta `201` con el ticket:

```json
{
  "id": "tkt_OufpKqnrvx9p6hW1yQbT3mZr8sKdLx0VeN",
  "object": "ticket",
  "livemode": false,
  "issuer": "iss_OufpKqnrvx9p",
  "folio": "A1042",
  "code": "A1042-K7QX",
  "status": "pending",
  "url": "https://fiscalisto.com/autofactura/Tx8mQ2vLp9WnR4c?c=A1042-K7QX",
  "total": 580,
  "tip": 0,
  "payment_form": "04",
  "description": "Consumo de alimentos",
  "product_key": "90101501",
  "sold_at": "2026-10-24T14:32:00-06:00",
  "invoiceable_until": "2026-10-31",
  "invoice": null,
  "global_invoice": null,
  "customer": null,
  "created_at": "2026-10-24T14:32:05-06:00"
}
```

El folio es único por emisor: registrar otra vez el mismo folio con el mismo total contesta el mismo ticket, así que puede reintentar sin miedo. Con otro total contesta `409 ticket_folio_taken`.

## Que su comprador se facture

Tiene tres formas, todas con el mismo portal del emisor:

1. **El código en el ticket.** Imprima `code` y el portal del emisor (`portal_url` de [`GET /v1/issuers/{id}/self_invoicing`](https://fiscalisto.com/docs/api/referencia/autofactura/get-self-invoicing)). El comprador escribe el código y el total.
2. **La liga.** Mande `url` por correo o WhatsApp: ya trae el código, el comprador solo pone el total.
3. **Dentro de su página**, sin que el comprador salga de ella, con `fiscalisto.js` o con React (abajo).

Cuando el comprador se factura llega `ticket.invoiced` (y `invoice.stamped`), y el ticket trae la factura en `invoice`. Si entra en la factura global llega `ticket.included_in_global`, con la factura global en `global_invoice`.

## En su página con fiscalisto.js

```html
<script src="https://fiscalisto.com/js/fiscalisto.js" defer></script>

<button data-fiscalisto-open="https://fiscalisto.com/autofactura/Tx8mQ2vLp9WnR4c?c=A1042-K7QX">
  Facturar mi compra
</button>
```

El botón abre el portal en una ventana encima de su página. Desde JavaScript:

```js
Fiscalisto.open(ticket.url, {
  onComplete: (e) => console.log("Factura timbrada", e.uuid),
  onClose: () => console.log("Cerró la ventana"),
});

// O dentro de un elemento, tan alto como su contenido:
Fiscalisto.mount("#factura", ticket.url, { onComplete: (e) => {} });
```

Funciona igual con la liga de una [solicitud de datos fiscales](https://fiscalisto.com/docs/api/datos-del-cliente) (`customer_data_request.url`): `onComplete` llega con `{ kind: "customer_data" }`.

## En React

```bash
npm install @fiscalisto/react
```

```tsx
import { FiscalistoEmbed, useFiscalistoDialog } from "@fiscalisto/react";

// Dentro de su página:
<FiscalistoEmbed url={ticket.url} onComplete={(e) => console.log(e)} />;

// En una ventana encima:
const { open, dialog } = useFiscalistoDialog({ heading: "Facture su compra" });
<button onClick={() => open(ticket.url)}>Facturar</button>;
{dialog}
```

> **Qué llega a su página.** Solo que el comprador terminó, y el UUID de su factura. Sus datos fiscales nunca pasan por su página: léalos con `GET /v1/tickets/{id}` o con el webhook `ticket.invoiced`. Solo se pueden mostrar así las ligas de autofactura y de datos fiscales; la liga de alta de un emisor, que pide su e.firma, siempre se abre aparte.

## El plazo y la factura global

Por omisión un ticket se factura hasta el fin del mes de la venta (lo usual ante el SAT) y la factura global es mensual. Para cambiarlo:

```bash tab="cURL"
curl https://api.fiscalisto.com/v1/issuers/iss_OufpKqnrvx9p/self_invoicing \
  -H "Authorization: Bearer $FISCALISTO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "window": "days", "window_days": 7, "global_periodicity": "02" }'
```

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

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

const selfInvoicing = await fiscalisto.issuers.updateSelfInvoicing("iss_OufpKqnrvx9p", {
  window: "days",
  window_days: 7,
  global_periodicity: "02",
});
```

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

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

self_invoicing = fiscalisto.issuers.update_self_invoicing("iss_OufpKqnrvx9p", {
    "window": "days",
    "window_days": 7,
    "global_periodicity": "02",
})
```

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

use Fiscalisto\Fiscalisto;

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

$selfInvoicing = $fiscalisto->issuers->updateSelfInvoicing('iss_OufpKqnrvx9p', [
    'window' => 'days',
    'window_days' => 7,
    'global_periodicity' => '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()

	selfInvoicing, err := client.Issuers.UpdateSelfInvoicing(ctx, "iss_OufpKqnrvx9p", &fiscalisto.UpdateSelfInvoicingParams{
		Window:            fiscalisto.String("days"),
		WindowDays:        fiscalisto.Int64(7),
		GlobalPeriodicity: fiscalisto.String("02"),
	})
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("%+v\n", *selfInvoicing)
}
```

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

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

var selfInvoicing = fiscalisto.issuers().updateSelfInvoicing("iss_OufpKqnrvx9p", new UpdateSelfInvoicingParams()
        .setWindow("days")
        .setWindowDays(7)
        .setGlobalPeriodicity("02"));
System.out.println(selfInvoicing);
```

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

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

var selfInvoicing = await fiscalisto.Issuers.UpdateSelfInvoicingAsync("iss_OufpKqnrvx9p", new UpdateSelfInvoicingParams
{
    Window = "days",
    WindowDays = 7,
    GlobalPeriodicity = "02",
});
```

`window: "days"` da `window_days` días desde la venta; `global_periodicity` es la c_Periodicidad de la factura global (01 diaria, 02 semanal, 03 quincenal, 04 mensual). Cada ticket dice hasta cuándo se puede facturar en `invoiceable_until`.

## Cancelar un ticket

Una venta que no se hizo (una devolución, un error de caja) se cancela con `DELETE /v1/tickets/{id}` mientras siga pendiente: ya no se puede facturar ni entra en la factura global. Uno que ya se facturó contesta `ticket_not_pending`; cancele su factura con [`POST /v1/invoices/{id}/cancel`](https://fiscalisto.com/docs/api/referencia/facturas/cancel-invoice).

## Listar

`GET /v1/tickets` da los tickets del más reciente al más antiguo por fecha de venta, con `status` (pending, invoiced, global, cancelled) y `sold_at_gte` / `sold_at_lte` para filtrar.

En pruebas todo funciona igual con su llave `fk_test_`: los tickets se facturan sin costo en el ambiente de pruebas del PAC y las facturas no son CFDI reales.
