# Factura.com Setup & Invoice Generation Flow

## Overview

Before a client can stamp official CFDI invoices, they must be registered as a sub-account (empresa) inside Factura.com. This is a one-time setup triggered from the "Datos de Facturación" settings page. Once complete, the client holds their own Factura.com API credentials and all subsequent invoice calls use those credentials directly.

---

## Step 1 — Fill in Fiscal Data

**Controller:** `app/Http/Controllers/InvoiceDataController.php` — `update()`  
**View:** `resources/views/admin/pages/configuration/invoice.blade.php`  
**Permission gate:** `editar datos de facturacion`

The client fills in their SAT fiscal identity:

| Field | Purpose |
|-------|---------|
| `tipo_persona` | Física or Moral |
| `razon_social` | Legal business name (max 50 chars) |
| `cp_fiscal` | 5-digit postal code of fiscal address |
| `rfc` | Mexican tax ID (max 15 chars) |
| `regimen_id` | SAT tax regime (FK → `sat_regimenes`) |
| `uso_id` | Default CFDI use (FK → `sat_usos_cfdi`) |

All fields are required. Saved directly to the `clients` table.

---

## Step 2 — Enable Invoice Generation & Upload e-firma Files

If the client checks **"Generar facturas"** (`make_invoices = on`), three additional fields become required:

| Field | Purpose |
|-------|---------|
| `file_cer` | SAT digital certificate (`.cer` file, max 2 MB) |
| `file_key` | SAT private key (`.key` file, max 2 MB) |
| `passcode` | Certificate password |

**What happens on upload:**
- Old `.cer` / `.key` files are deleted from storage if they exist
- New files are saved to `storage/app/public/certificados_cfdi/` with randomized filenames
- Filenames stored in `client.file_cer` and `client.file_key`
- Passcode is base64-encoded and stored in `client.passcode`
- `client.make_invoices` is set to `1`

If the toggle is turned **off**, all three fields are cleared (`NULL`) and `make_invoices` is set to `0`.

---

## Step 3 — Register the Client as a Factura.com Contact (factura_uid)

After saving fiscal data, the system syncs the client's billing identity to Factura.com using the **platform's global credentials** (from `.env`):

- If `client.factura_uid` is empty → `facturaCreateClient()` → `POST /v1/clients/create`
- If `client.factura_uid` exists → `facturaUpdateClient()` → `POST /v1/clients/{uid}/update`

Payload sent: `rfc`, `razons`, `codpos`, `email`, `usocfdi`, `regimen`, `pais` (`MEX`).

On success: the returned `UID` is saved to `client.factura_uid`.

> This step registers the client as a **receivable contact** in Factura.com (i.e. who invoices get issued to). It is separate from the company/empresa registration in Step 4.

---

## Step 4 — Create the Empresa in Factura.com (empresa_uid)

This step only runs if `file_cer`, `file_key`, and `passcode` are all present on the client.

The system calls `FacturaService::facturaCreateCompany()` or `facturaUpdateCompany()` using the **platform's global credentials**:

- If `client.empresa_uid` is empty → `facturaCreateCompany()` → `POST /v4/account/create`
- If `client.empresa_uid` exists → `facturaUpdateCompany()` → `POST /v4/account/{uid}/update`

**Payload (multipart form):**

| Field | Source |
|-------|--------|
| `rfc` | `client.rfc` |
| `razons` | `client.razon_social` |
| `codpos` | `client.cp_fiscal` |
| `email` | logged-in user's email |
| `regimen` | SAT regimen clave |
| `password` | `base64_decode(client.passcode)` |
| `cer` *(file)* | `client.file_cer` from storage |
| `key` *(file)* | `client.file_key` from storage |
| `logo` *(file, optional)* | `client.logo` from storage |

**On success**, Factura.com returns per-client API credentials:

| Returned field | Saved to client as |
|----------------|--------------------|
| `acco_uid` | `empresa_uid` |
| `acco_api_key` | `empresa_api_key` (stored as-is — Factura.com already returns these base64-encoded) |
| `acco_secret_key` | `empresa_secret_key` (stored as-is — same reason) |

---

## Step 5 — Fetch Invoice Series

Immediately after the empresa is created or updated, the system calls `facturaListSeries($client_id)` → `GET /v4/series` using the **client's own** new API credentials.

Series IDs are matched by name and saved:

| Series name | Saved to |
|-------------|----------|
| `F` | `client.f_serie` |
| `N` | `client.n_serie` |
| `R` | `client.r_serie` |

These series IDs are included in every CFDI stamping request.

---

## Edge Case — Certificate Files Upload Failure

Factura.com may create the empresa account successfully but fail to process the certificate files. In this case it returns a warning with an `acco_uid` but no API keys.

**Fallback flow:**
1. `facturaCompanyInfo($empresaUID)` is called → `GET /v1/account/{uid}` to retrieve the API keys separately
2. `empresa_uid`, `empresa_api_key`, `empresa_secret_key` are saved from the info response
3. The local `.cer` and `.key` files are deleted from storage and cleared from the database (`file_cer`, `file_key`, `passcode` → `null`)
4. A warning is returned to the UI — the client will need to re-upload their certificate files

---

## Step 6 — Client Is Ready to Invoice

Once `empresa_uid` is set on the client:
- `ClientController::checkEmpresaUID()` returns `hasEmpresaUID: true`
- No warning banner is shown on the dashboard
- The "Generate Invoice" action becomes available on approved quotes

---

## Step 7 — Stamping Invoices (CFDI)

All CFDI operations use the **client's own** `empresa_api_key` / `empresa_secret_key`:

| Operation | Method | Endpoint |
|-----------|--------|----------|
| Stamp invoice | `facturaCreateInvoice()` | `POST /v4/cfdi40/create` |
| Save as draft | `facturaCreateInvoice($invoice, draft=1)` | `POST /v4/cfdi40/create` |
| Update draft | `facturaUpdateDraft()` | `POST /v4/cfdi40/create/{draftUID}` |
| Promote draft → invoice | `facturaDraftToInvoice()` | `POST /v4/cfdi40/{draftUID}/timbrarborrador` |
| Cancel invoice | `facturaCancelInvoice()` | `POST /v4/cfdi40/{uid}/cancel` |
| Check cancel status | `facturaCheckCancelStatus()` | `GET /v4/cfdi40/{uid}/cancel_status` |
| Download PDF/XML | `facturaDownload()` | `GET /v4/cfdi40/{uid}/{pdf\|xml}` |
| Download cancel receipt | `acuseDownload()` | `GET /v4/cfdi40/{uid}/acuse` |

**Environment routing:** `isTestPackage()` determines whether to hit the sandbox or production Factura.com URL and credentials.

**Credential fallback:** If `invoice.subclient_id` is empty, the platform's global API key is used instead of the client's.

---

## Credential Routing Summary

| Operation | Credentials Used |
|-----------|-----------------|
| Register client contact (`factura_uid`) | Platform global (`.env`) |
| Create / update empresa (`empresa_uid`) | Platform global (`.env`) |
| Fetch company info / series | Platform global (`.env`) |
| Stamp / cancel / download invoices | Client's own `empresa_api_key` + `empresa_secret_key` |

---

## Key Fields on the `clients` Table

| Field | Set when |
|-------|----------|
| `factura_uid` | Client contact created/updated in Factura.com (Step 3) |
| `empresa_uid` | Empresa (sub-account) created in Factura.com (Step 4) |
| `empresa_api_key` | Returned on empresa create/update (base64-encoded) |
| `empresa_secret_key` | Returned on empresa create/update (base64-encoded) |
| `file_cer`, `file_key` | SAT e-firma files uploaded (cleared if upload fails) |
| `passcode` | Certificate password, base64-encoded |
| `make_invoices` | `1` when invoice generation is enabled |
| `f_serie`, `n_serie`, `r_serie` | Invoice series IDs fetched from Factura.com |
