# Pruebas y producción

> Pruebe gratis con llaves fk_test_ y pase a producción con su emisor, sus timbres y una llave fk_live_.

Su cuenta API tiene dos modos que no se mezclan: cada uno tiene sus llaves, sus emisores, sus clientes, sus facturas, sus webhooks y sus eventos. La llave con la que llama decide el modo; cada objeto lo dice en `livemode`.

| | Pruebas | Producción |
| --- | --- | --- |
| Llave | `fk_test_…` | `fk_live_…` |
| Validez ante el SAT | Ninguna: no son CFDI reales | CFDI reales |
| Costo | Gratis | Un timbre por comprobante |
| Emisor | Cualquier RFC; se timbra como el emisor de pruebas del SAT | Su RFC, con su CSD y su e.firma |
| Límite de solicitudes | 100 cada 10 segundos | 100 cada 10 segundos |

## Pruebas

Al abrir su cuenta API ya tiene un emisor de pruebas y una llave `fk_test_`: puede timbrar en ese momento. En pruebas se aplican las mismas revisiones del SAT que en producción (catálogos, régimen contra uso, totales, complementos) y el PAC timbra en su ambiente de pruebas, así que una factura que sale bien en pruebas sale bien en producción.

Puede crear emisores de pruebas con cualquier RFC para imitar su configuración real, pero no se suben archivos: el ambiente de pruebas solo acepta el certificado de pruebas del SAT, así que todo se timbra como `EKU9003173C9` (el campo `stamps_as` del emisor lo dice). Los clientes, en cambio, pueden ser reales o de prueba.

Pruebas es gratis, con dos cuidados: una cuenta timbra hasta 1,000 comprobantes de prueba al día (vea [Límites](https://fiscalisto.com/docs/api/limites)), y los correos de las facturas de prueba solo llegan a las personas de su cuenta API, para probar `send_email` en su propio correo sin escribirle a sus clientes.

### Provocar cada rechazo del SAT

Para probar cómo responde su sistema cuando el SAT rechaza una factura, use uno de estos RFC como cliente con su llave de pruebas. La API responde `422` con `invoice_rejected`, el `sat_code` y el mensaje para su usuario, exactamente como en producción, y le llega `invoice.failed` a sus webhooks. Los demás datos pueden ser cualquiera; con otros RFC la factura se timbra.

| RFC del cliente | `sat_code` | Qué simula |
| --- | --- | --- |
| `NOM010101FK9` | `CFDI40145` | El nombre del cliente no corresponde con su RFC. |
| `CPO010101FK1` | `CFDI40147` | El código postal del cliente no es el de su domicilio fiscal. |
| `REG010101FK9` | `CFDI40157` | El régimen fiscal del cliente no es el que tiene en el SAT. |
| `NOI010101FK5` | `CFDI40143` | El RFC del cliente no está inscrito en el SAT. |
| `USO010101FK4` | `CFDI40161` | El uso del CFDI no corresponde al régimen del cliente. |

```bash tab="cURL"
curl https://api.fiscalisto.com/v1/invoices \
  -H "Authorization: Bearer $FISCALISTO_API_KEY" \
  -H "Idempotency-Key: prueba-rechazo-1" \
  -H "Content-Type: application/json" \
  -d '{
    "customer": { "tax_id": "NOM010101FK9", "legal_name": "CLIENTE DE PRUEBA", "tax_system": "601", "zip": "33826" },
    "payment_form": "03",
    "items": [{ "description": "Consultoría", "product_key": "80101500", "unit_price": 1000 }]
  }'
```

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

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

const invoice = await fiscalisto.invoices.create({
  customer: {
    tax_id: "NOM010101FK9",
    legal_name: "CLIENTE DE PRUEBA",
    tax_system: "601",
    zip: "33826",
  },
  payment_form: "03",
  items: [
    {
      description: "Consultoría",
      product_key: "80101500",
      unit_price: 1000,
    },
  ],
}, { idempotencyKey: "prueba-rechazo-1" });
```

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

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

invoice = fiscalisto.invoices.create({
    "customer": {
        "tax_id": "NOM010101FK9",
        "legal_name": "CLIENTE DE PRUEBA",
        "tax_system": "601",
        "zip": "33826",
    },
    "payment_form": "03",
    "items": [
        {
            "description": "Consultoría",
            "product_key": "80101500",
            "unit_price": 1000,
        },
    ],
}, idempotency_key="prueba-rechazo-1")
```

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

use Fiscalisto\Fiscalisto;

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

$invoice = $fiscalisto->invoices->create([
    'customer' => [
        'tax_id' => 'NOM010101FK9',
        'legal_name' => 'CLIENTE DE PRUEBA',
        'tax_system' => '601',
        'zip' => '33826',
    ],
    'payment_form' => '03',
    'items' => [
        [
            'description' => 'Consultoría',
            'product_key' => '80101500',
            'unit_price' => 1000,
        ],
    ],
], ['idempotency_key' => 'prueba-rechazo-1']);
```

```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.Create(ctx, &fiscalisto.InvoiceCreate{
		Customer: fiscalisto.ObjectOf(fiscalisto.InvoiceCreateCustomer{
			TaxID:     "NOM010101FK9",
			LegalName: "CLIENTE DE PRUEBA",
			TaxSystem: "601",
			Zip:       "33826",
		}),
		PaymentForm: fiscalisto.String("03"),
		Items: []fiscalisto.InvoiceCreateItem{
			{
				Description: fiscalisto.String("Consultoría"),
				ProductKey:  fiscalisto.String("80101500"),
				UnitPrice:   fiscalisto.Float64(1000),
			},
		},
	}, fiscalisto.WithIdempotencyKey("prueba-rechazo-1"))
	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().create(new InvoiceCreate()
        .setCustomer(new InvoiceCreateCustomer()
                .setTaxId("NOM010101FK9")
                .setLegalName("CLIENTE DE PRUEBA")
                .setTaxSystem("601")
                .setZip("33826"))
        .setPaymentForm("03")
        .setItems(List.of(
                new InvoiceCreateItem()
                        .setDescription("Consultoría")
                        .setProductKey("80101500")
                        .setUnitPrice(1000)
        )), RequestOptions.idempotencyKey("prueba-rechazo-1"));
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.CreateAsync(new InvoiceCreate
{
    Customer = new InvoiceCreateCustomer
    {
        TaxId = "NOM010101FK9",
        LegalName = "CLIENTE DE PRUEBA",
        TaxSystem = "601",
        Zip = "33826",
    },
    PaymentForm = "03",
    Items = new()
    {
        new()
        {
            Description = "Consultoría",
            ProductKey = "80101500",
            UnitPrice = 1000,
        },
    },
}, new RequestOptions { IdempotencyKey = "prueba-rechazo-1" });
```

Solo funcionan en pruebas: en producción esos RFC no existen y el SAT los rechaza por eso.

### Probar desde la documentación

Cada página de la [referencia](https://fiscalisto.com/docs/api/referencia/facturas/create-invoice) tiene **Probar**: cambie los campos y pulse **Enviar**. Si entró a Fiscalisto con su cuenta API abierta (o tiene solo una) no necesita llave: la solicitud corre como esa cuenta, en pruebas, y aparece en **Solicitudes** como «Documentación». Si no, ponga su llave `fk_test_…` en **Authorization**. La solicitud llega a la API de verdad (timbra en pruebas, le manda eventos a sus webhooks de pruebas) y ve la respuesta completa con sus encabezados. La llave se queda solo en su navegador. Ahí no se aceptan llaves `fk_live_`: lo de producción se llama desde su servidor.

### Empezar de cero

Para que sus pruebas automáticas siempre arranquen igual, reinicie el modo de pruebas: cada emisor de pruebas conserva su RFC y su CSD de pruebas, y se borran sus facturas, clientes, productos, lotes, ligas y descargas, además de los eventos y las solicitudes de pruebas. Las llaves y los webhooks se quedan. Con `seed` (por omisión) quedan 3 clientes y 3 productos de ejemplo en cada emisor.

```bash tab="cURL"
curl https://api.fiscalisto.com/v1/test_data/reset \
  -H "Authorization: Bearer $FISCALISTO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "seed": true }'
```

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

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

const testDataReset = await fiscalisto.testData.reset({ seed: true });
```

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

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

test_data_reset = fiscalisto.test_data.reset({"seed": True})
```

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

use Fiscalisto\Fiscalisto;

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

$testDataReset = $fiscalisto->testData->reset(['seed' => true]);
```

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

	testDataReset, err := client.TestData.Reset(ctx, &fiscalisto.ResetTestDataParams{Seed: fiscalisto.Bool(true)})
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("%+v\n", *testDataReset)
}
```

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

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

var testDataReset = fiscalisto.testData().reset(new ResetTestDataParams().setSeed(true));
System.out.println(testDataReset);
```

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

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

var testDataReset = await fiscalisto.TestData.ResetAsync(new ResetTestDataParams { Seed = true });
```

Solo con una llave `fk_test_` (con `fk_live_` responde `test_mode_only`) y hasta 20 veces al día. También está en **Desarrolladores › Inicio**, en **Datos de prueba**.

### Un entorno de prueba para cada persona

Si varias personas o proyectos usan la misma cuenta, dele a cada uno su entorno de prueba: nace con su propio emisor de pruebas del SAT y una llave `fk_test_` que solo ve los emisores, facturas, webhooks y eventos de ese entorno. Así el reinicio de una persona no le borra nada a otra, y su webhook no recibe los eventos de los demás. Las llaves de pruebas de la cuenta tampoco ven lo que hay en un entorno.

```bash tab="cURL"
curl https://api.fiscalisto.com/v1/sandboxes \
  -H "Authorization: Bearer $FISCALISTO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Ana" }'
```

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

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

const sandbox = await fiscalisto.sandboxes.create({ name: "Ana" });
```

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

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

sandbox = fiscalisto.sandboxes.create({"name": "Ana"})
```

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

use Fiscalisto\Fiscalisto;

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

$sandbox = $fiscalisto->sandboxes->create(['name' => 'Ana']);
```

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

	sandbox, err := client.Sandboxes.Create(ctx, &fiscalisto.CreateSandboxParams{Name: "Ana"})
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(sandbox.ID)
}
```

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

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

var sandbox = fiscalisto.sandboxes().create(new CreateSandboxParams().setName("Ana"));
System.out.println(sandbox.getId());
```

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

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

var sandbox = await fiscalisto.Sandboxes.CreateAsync(new CreateSandboxParams { Name = "Ana" });
```

La respuesta trae `key.secret` una sola vez: es la llave del entorno. Con ella, `POST /v1/test_data/reset` vacía solo ese entorno, y tiene sus propios 20 reinicios al día. Los entornos se crean, listan y borran con una llave de pruebas de la cuenta (una del entorno responde `key_in_sandbox`), hasta 25 por cuenta. Al borrar uno, sus llaves dejan de funcionar al momento. También están en **Desarrolladores › Llaves**, en **Entornos de prueba**.

### Adelantar el tiempo con un reloj de prueba

Una factura recurrente mensual, una cotización que vence o la factura global de los tickets del mes tardan semanas en pasar. Con un reloj de prueba las ve en segundos: el emisor de pruebas vive en la hora del reloj, que solo avanza cuando usted lo adelanta.

Primero el reloj, congelado ahora (o en `frozen_time`):

```bash tab="cURL"
curl https://api.fiscalisto.com/v1/issuers/iss_OufpKqnrvx9p/test_clock \
  -H "Authorization: Bearer $FISCALISTO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{}'
```

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

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

const testClock = await fiscalisto.testClocks.create("iss_OufpKqnrvx9p");
```

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

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

test_clock = fiscalisto.test_clocks.create("iss_OufpKqnrvx9p")
```

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

use Fiscalisto\Fiscalisto;

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

$testClock = $fiscalisto->testClocks->create('iss_OufpKqnrvx9p');
```

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

	testClock, err := client.TestClocks.Create(ctx, "iss_OufpKqnrvx9p", nil)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(testClock.ID)
}
```

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

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

var testClock = fiscalisto.testClocks().create("iss_OufpKqnrvx9p");
System.out.println(testClock.getId());
```

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

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

var testClock = await fiscalisto.TestClocks.CreateAsync("iss_OufpKqnrvx9p");
```

Cree sus facturas recurrentes, cotizaciones o tickets como siempre, y adelántelo, por ejemplo tres meses:

```bash tab="cURL"
curl https://api.fiscalisto.com/v1/issuers/iss_OufpKqnrvx9p/test_clock/advance \
  -H "Authorization: Bearer $FISCALISTO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "frozen_time": "2027-01-25T09:00:00-06:00" }'
```

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

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

const testClock = await fiscalisto.testClocks.advance("iss_OufpKqnrvx9p", { frozen_time: "2027-01-25T09:00:00-06:00" });
```

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

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

test_clock = fiscalisto.test_clocks.advance("iss_OufpKqnrvx9p", {"frozen_time": "2027-01-25T09:00:00-06:00"})
```

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

use Fiscalisto\Fiscalisto;

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

$testClock = $fiscalisto->testClocks->advance('iss_OufpKqnrvx9p', ['frozen_time' => '2027-01-25T09:00: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()

	testClock, err := client.TestClocks.Advance(ctx, "iss_OufpKqnrvx9p", &fiscalisto.AdvanceTestClockParams{
		FrozenTime: "2027-01-25T09:00:00-06:00",
	})
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(testClock.ID)
}
```

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

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

var testClock = fiscalisto.testClocks().advance("iss_OufpKqnrvx9p", new AdvanceTestClockParams()
        .setFrozenTime("2027-01-25T09:00:00-06:00"));
System.out.println(testClock.getId());
```

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

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

var testClock = await fiscalisto.TestClocks.AdvanceAsync("iss_OufpKqnrvx9p", new AdvanceTestClockParams
{
    FrozenTime = "2027-01-25T09:00:00-06:00",
});
```

Al adelantarlo se hace, día por día, lo que habría pasado: se timbra cada periodo de las facturas recurrentes (con `{mes}` y `{año}` de ese periodo), vencen las cotizaciones y salen las facturas globales de tickets, cada cosa con sus webhooks. La respuesta llega cuando terminó, con lo que hizo en `advanced`, y además llega el evento `test_clock.ready`.

- Mientras tenga reloj, los procesos diarios no tocan al emisor: nada pasa solo.
- El reloj llega hasta dos años adelante. Cada llamada timbra hasta 25 comprobantes; si toca más, se detiene al final de ese día con `stopped_early: true`, y otra llamada sigue desde ahí.
- Los CFDI se timbran con la fecha y hora reales, porque el SAT solo acepta las de hoy. El periodo, los vencimientos, `amount_due` y los eventos sí siguen al reloj.
- Las ligas que abren sus clientes (cotizaciones, autofactura) van con la hora real.
- `DELETE /v1/issuers/iss_…/test_clock` regresa al emisor a la hora real.

Solo los emisores de prueba tienen reloj, uno cada uno, y se adelanta hasta 50 veces al día.

> **No mezcle los modos.** Un id de un modo no existe en el otro. Si usa una llave `fk_live_` con un `inv_…` de pruebas, la API responde `livemode_mismatch`.

## Pasar a producción

1. **Agregue su emisor** con [`POST /v1/issuers`](https://fiscalisto.com/docs/api/referencia/emisores/create-issuer) usando una llave de producción, o en el panel en **Emisores**. Los datos van tal como están en la constancia de situación fiscal.
2. **Suba su CSD** (certificado de sello digital: `.cer`, `.key` y contraseña) con [`POST /v1/issuers/{id}/certificates`](https://fiscalisto.com/docs/api/referencia/emisores/upload-certificate).
3. **Firme el contrato con el PAC** con la e.firma del RFC: [`POST /v1/issuers/{id}/efirma`](https://fiscalisto.com/docs/api/referencia/emisores/sign-efirma). Es una sola vez por RFC.
4. **Tenga timbres.** Al abrir su primera cuenta API recibe 20 de regalo; después compre paquetes en **Desarrolladores › Timbres**. Consulte los suyos con [`GET /v1/balance`](https://fiscalisto.com/docs/api/referencia/cuenta/get-balance).
5. **Cambie la llave** de su servidor por una `fk_live_` (se crea en **Desarrolladores › Llaves** y pide verificación en dos pasos).

El emisor está listo cuando su `status` es `active`. Mientras no lo esté, `requirement` dice qué falta. Vea [Emisores](https://fiscalisto.com/docs/api/emisores).

## Qué cuesta un timbre

Cada comprobante timbrado en producción usa un timbre: factura, complemento de pago, nota de crédito, anticipo, recibo de nómina o constancia de retenciones. Cancelar un comprobante también usa uno, solo la primera vez que el SAT acepta la solicitud. No usan timbre las solicitudes que no pasan la revisión, las que el SAT o el PAC rechazan ni las repeticiones con la misma `Idempotency-Key`.

Cuando ya no quedan, la API responde `402` con `insufficient_balance` y no timbra. Compre un paquete y repita la solicitud con la misma `Idempotency-Key`.

Para que no lleguen a acabarse: con pocos timbres le llega el evento `balance.low` a sus webhooks y un correo (la cantidad se elige en **Desarrolladores › Timbres**, 100 si no la cambia), y otra vez al acabarse. Ahí mismo puede activar la **Recarga automática**: compramos el paquete que elija con su tarjeta cuando queden los que diga, hasta las veces al mes que diga. `GET /v1/balance` trae ambas cosas en `low_balance_at` y `auto_recharge`. Comprar timbres no se puede con una llave de la API, a propósito: una llave filtrada no puede hacer cargos a su tarjeta.
