Facturación FIZ
Emite facturas con la API pública de FIZ — una puerta de enlace REST en https://api.fiz.co.
Este skill guía a través del ciclo de vida completo: buscar o crear el cliente, buscar o crear los artículos, construir un borrador de factura, emitirla (este es el paso legalmente vinculante) y, opcionalmente, descargar el PDF.
Configuración
Cada solicitud necesita la clave API del cliente en la cabecera x-api-key. Obtén una clave en https://app.fiz.co/settings/integrations.
Guárdala en una variable de entorno para que nunca se codifique directamente en los comandos:
export FIZ_API_KEY="fiz_api_..." # la clave del usuario desde app.fiz.co
export FIZ_API_URL="https://api.fiz.co" # opcional; este es el valor por defecto
Todos los ejemplos siguientes usan $FIZ_API_KEY y $FIZ_API_URL. Si FIZ_API_URL no está definido, usa https://api.fiz.co. Si FIZ_API_KEY no está definido, detente e indica al usuario que la configure en su entorno (export FIZ_API_KEY=… en su terminal, o un gestor de secretos) — no le pidas que pegue la clave en el chat y nunca imprimas ni muestres el valor de la clave. Una clave API es una credencial; mantenla fuera de la transcripción de la conversación.
Se proporciona un script reutilizable curl en scripts/fiz.sh. Los comandos se ejecutan desde el directorio de trabajo de la sesión (la raíz de tu proyecto), no desde el directorio del skill, así que carga el script usando su ruta absoluta a través de ${CLAUDE_SKILL_DIR} (Claude Code establece esta variable en el directorio propio del skill):
source "${CLAUDE_SKILL_DIR}/scripts/fiz.sh" # se resuelve desde cualquier directorio de trabajo
fiz GET /invoices
En un entorno de ejecución que no proporcione CLAUDE_SKILL_DIR, sustituye esa variable por la ruta equivalente al directorio del skill (una ruta relativa simple scripts/fiz.sh no se resolverá desde la raíz del proyecto).
El script auxiliar imprime el código de estado HTTP y devuelve un valor distinto de cero en errores 4xx/5xx, por lo que una respuesta de error nunca se confunde con un éxito; consulta la sección "Manejo de errores" más abajo. También se muestran ejemplos con curl sin formato; si usas curl directamente, comprueba siempre el código de estado HTTP, no solo si el comando terminó con código 0.
El modelo mental
- Una factura referencia un cliente (por su id) y uno o más artículos (por su id). Por lo tanto, los clientes y los artículos deben existir antes de que puedas construir la factura.
POST /invoicescrea un borrador. Un borrador aún no es un documento legal — no se envía a la autoridad tributaria y se puede editar o eliminar libremente.POST /invoices/:id/issuelo emite — asigna el número oficial y el ATCUD, sincroniza con la Autoridad Tributaria portuguesa (AT) y lo hace definitivo. La emisión es irreversible; confirma con el usuario antes de emitir. Una factura emitida no se puede eliminar; la única forma de deshacerla es una nota de crédito.- La única excepción a "crear y luego emitir": si pasas un objeto
paymental crear para los tiposINVOICE_RECEIPToSIMPLIFIED_INVOICE, el documento registra el pago — pero igualmente tienes que emitirlo para finalizarlo.
Confirmación antes de escrituras
Estos endpoints cambian el estado en la cuenta del usuario. Dos niveles de cuidado:
- Planifica, luego previsualiza todas las escrituras antes de la primera. Una vez que sepas lo que necesita la solicitud, enumera todas las escrituras que planeas realizar para esta tarea — cada endpoint más su carga JSON (el cliente a crear, cada artículo, el borrador de factura) — y muestra ese lote al usuario en una sola previsualización. Procede con las escrituras solo después de que el usuario apruebe el plan. Si el plan cambia durante la ejecución (por ejemplo, una búsqueda de artículo falla y ahora necesitas crear uno), previsualiza la nueva escritura antes de enviarla. Nunca inventes datos del cliente, campos fiscales o cantidades; usa solo lo que el usuario te haya dado.
- Antes de
POST /invoices/:id/issue— esto es irreversible y se reporta a la autoridad tributaria. Solicita siempre una confirmación explícita y separada (más allá de la aprobación del plan anterior), mostrando el cliente del borrador, las líneas de detalle, el IVA y el total. No emitas por iniciativa propia.
Política de invocación (deliberada): este skill mantiene la auto-invocación del modelo habilitada — la description es la superficie de descubrimiento, y la red de seguridad real es la previsualización de todas las escrituras + la confirmación separada de emisión anterior, no la restricción del descubrimiento. El manifiesto de Codex (agents/openai.yaml) está configurado para coincidir (allow_implicit_invocation: true). Un operador que quiera un uso solo manual puede establecer disable-model-invocation: true en la portada (se carga solo en /fiz-invoicing).
Esto es facturación fiscal portuguesa — por qué la corrección es importante
FIZ emite documentos fiscales legalmente vinculantes que se reportan a la Autoridad Tributaria portuguesa (Autoridade Tributária, "AT"). Un tipo de IVA incorrecto o un motivo de exención faltante no es un error cosmético — produce un documento fiscal incorrecto que, una vez emitido, solo puede corregirse con una nota de crédito. Asegúrate de que los campos fiscales sean correctos antes de emitir.
Las dos cosas que tienen más probabilidades de estar mal, y sobre las que debes razonar:
1. Tipo de IVA (vatRate en cada artículo) — nunca lo adivines. La banda de tasa (NORMAL | INTERMEDIATE | REDUCED | EXEMPT) y el porcentaje al que corresponde dependen del territorio (Continental / Açores / Madeira). Usa NORMAL por defecto — REDUCED/INTERMEDIATE se aplican solo a categorías legalmente definidas específicas (ciertos alimentos, restaurantes, eventos culturales…). Si el usuario no ha nombrado dicha categoría, usa NORMAL e indícalo, o pregunta; nunca apliques una tasa reducida en silencio. La tabla de porcentajes por territorio está en domain.md.
2. La exención de IVA requiere un código de motivo (vatExemptionReason). Si vatRate es EXEMPT, la ley portuguesa exige un motivo de isenção — un código M que indique la base legal. Establécelo siempre; un artículo exento sin un motivo válido es un documento inválido y puede ser rechazado al emitir. El código debe reflejar el motivo real: régimen de pequeños empresarios → M10, actividad exenta por naturaleza (salud/educación) → M07, B2B intracomunitario / inversión del sujeto pasivo → M40/M16/M19. Si el usuario está exento pero no sabe el código, pregunta por qué y haz el mapeo — no elijas arbitrariamente. La lista completa de códigos con referencias legales está en domain.md.
Otras reglas del dominio que vale la pena conocer (detalles en domain.md):
- CAE es un código portugués de actividad económica de 5 dígitos que debe ser uno de los que la empresa tiene registrados. No lo inventes — pregúntalo al usuario, o reutiliza el CAE de una factura anterior. Debe ser un código actual (Rev. 4).
- NIF (
taxpayerNumber) es un número de identificación fiscal portugués de 9 dígitos con dígito de control; obligatorio para B2B, opcional para consumidores finales. Para B2B intracomunitario, el número de IVA del cliente es relevante para el tratamiento de inversión del sujeto pasivo. - Retención en la fuente (retenção na fonte: IRS/IRC/IS) se configura en el artículo; la tasa común es del 25% para servicios de autónomos. Es retenida por el pagador, reduciendo el importe realmente pagado.
- El tipo de documento afecta las reglas: un
SIMPLIFIED_INVOICEes para pequeñas ventas al por menor y puede omitir los datos completos del cliente; unaINVOICEa una empresa por más de 1.000 € necesita el NIF del cliente.
Flujo de trabajo
Sigue estos pasos. Omite los pasos 1–2 si el usuario ya te proporciona un id de cliente e ids de artículos.
Paso 1 — Resolver el cliente
Busca primero para evitar duplicados, luego crea si es necesario.
Los ejemplos usan el script auxiliar fiz (ver Configuración) — comprueba el código de estado HTTP por ti. El equivalente con curl sin formato está en el apéndice.
Buscar por término (nombre, número fiscal, correo electrónico):
fiz GET "/customers?search=Joao"
Crear un cliente (solo name es obligatorio):
fiz POST /customers '{
"name": "João Silva",
"email": "[email protected]",
"taxpayerNumber": "303741791",
"country": "PT"
}'
Conserva el id devuelto — ese es el customerId para la factura.
Paso 2 — Resolver los artículos (productos / servicios)
Buscar artículos existentes:
fiz GET "/items?search=consulting"
Reutiliza un artículo existente solo si sus campos fiscales coinciden con la venta. Una coincidencia de búsqueda con el nombre correcto no es suficiente — para un documento fiscal, los campos
vatRate,vatTerritory,vatExemptionReason,taxRatey de retención del artículo deben coincidir con el tratamiento que pretendes para este cliente y venta. Si alguno de ellos difiere (por ejemplo, el mismo servicio pero el cliente está exento, en otro territorio, o sujeto a retención), crea un nuevo artículo con los campos correctos en lugar de reutilizar el que no coincide. Cuando tengas dudas, inspecciona los campos del artículo encontrado y confírmalo con el usuario.
Crear un artículo. Obligatorio: name, type (PRODUCT | SERVICE), unitPrice, vatRate (NORMAL | INTERMEDIATE | REDUCED | EXEMPT):
fiz POST /items '{
"name": "Consulting hour",
"type": "SERVICE",
"unitPrice": 80,
"vatRate": "NORMAL"
}'
Conserva cada id devuelto — esos son los ids de los artículos para la factura.
unitPricees el precio unitario; los totales por línea son calculados por FIZ a partir de laquantityque proporciones en la factura. EligevatRatedeliberadamente — consulta la sección de dominio anterior; usaNORMALpor defecto a menos que la categoría claramente justifique una tasa reducida. SivatRateesEXEMPT, establece siempre un códigoMdevatExemptionReasonque coincida con por qué la venta está exenta (por ejemplo,M10para el régimen de pequeños empresarios). El campo es técnicamente opcional al crear el artículo, pero un artículo exento sin un motivo válido es incorrecto y puede ser rechazado más tarde cuando emitas la factura. Consultadomain.mdpara la lista completa de códigos.
Paso 3 — Crear el borrador de factura
Campos obligatorios: dueDate (ISO 8601), cae (código de actividad económica portugués), type, customerId e items (al menos un { id, quantity }).
fiz POST /invoices '{
"dueDate": "2026-07-18T00:00:00.000Z",
"cae": "62010",
"type": "INVOICE",
"customerId": "6863b1513117c5892ff55296",
"items": [
{ "id": "68483e978073231c3947077c", "quantity": 10 }
]
}'
La respuesta incluye el nuevo id de la factura (necesario para emitir) y status (DRAFT). La date de la factura se establece a la fecha actual y currency es EUR por parte de la API — no los envías.
Opcional — descuento global (summary):
"summary": { "globalDiscountType": "PERCENT", "globalDiscountPercent": 10 }
Usa "AMOUNT" con globalDiscountAmount para un descuento de valor fijo.
Opcional — pago en la creación (payment): solo permitido cuando type es INVOICE_RECEIPT o SIMPLIFIED_INVOICE; la API devuelve 400 en caso contrario.
"payment": { "method": "mbWay", "date": "2026-07-18T00:00:00.000Z" }
method es uno de: cash, card, bankTransfer, mbWay, multibanco, spin, other.
Paso 4 — Emitir la factura
Este es el paso vinculante. Confirma con el usuario, luego:
fiz POST /invoices/{id}/issue
La respuesta incluye el number oficial, status: ISSUED y un bloque syncWithAt. Comprueba syncWithAt.status — si es FAILED, la factura se creó localmente pero no se sincronizó con la autoridad tributaria; reporta atMessage y si isRetriable es true al usuario en lugar de afirmar éxito.
Paso 5 — Descargar el PDF (opcional)
fiz GET /invoices/{id}/pdf
Devuelve { id, name, url }; la url es un enlace descargable al PDF.
Añade ?format=A4 o ?format=RECEIPT para elegir el diseño.
Tipos de documento (type)
Valores: INVOICE · INVOICE_RECEIPT · SIMPLIFIED_INVOICE · RECEIPT · CREDIT_NOTE · DEBIT_NOTE. Usa INVOICE por defecto a menos que el usuario solicite un recibo o factura simplificada. Solo INVOICE_RECEIPT y SIMPLIFIED_INVOICE aceptan un objeto payment en la creación. Consulta domain.md para saber qué significa cada tipo y cuándo usarlo.
Referencia de operaciones comunes
| Acción | Método y ruta |
|---|---|
| Listar/buscar clientes | GET /customers?search=… |
| Crear cliente | POST /customers |
| Listar/buscar artículos | GET /items?search=… |
| Crear artículo | POST /items |
| Crear borrador | POST /invoices |
| Emitir | POST /invoices/:id/issue |
| Listar facturas | GET /invoices |
| Obtener una factura | GET /invoices/:id |
| Descargar PDF | GET /invoices/:id/pdf |
| Eliminar (borrador) | DELETE /invoices/:id |
Manejo de errores
Comprueba siempre el código de estado HTTP, no solo que el comando se ejecutó. En caso de error, la API devuelve un cuerpo JSON de apariencia normal — { "statusCode": 400, "message": "...", "timestamp": "..." } — con el código de estado HTTP correspondiente. Con curl -s sin formato, ese cuerpo se imprime y curl aún termina con código 0, por lo que es fácil confundir un error con un resultado. El script auxiliar fiz protege contra esto: imprime una línea HTTP <code> y devuelve un valor distinto de cero en errores 4xx/5xx. Si debes usar curl directamente, añade -w '\n%{http_code}' (o -o body -w '%{http_code}') e inspecciona el código.
Códigos comunes:
- 401 — clave API faltante o inválida. Indica al usuario que configure
FIZ_API_KEYen su entorno (no en el chat). - 400 — error de validación; el
messageindica qué campo. Casos comunes:itemsvacío,paymenten untypeno compatible, un campo desconocido o extra (lista blanca estricta),dueDatemal formado (debe ser ISO 8601 completa con zona horaria, por ejemplo2026-07-18T00:00:00.000Z). - 5xx — problema del backend/AT; comunícalo, no reintentes la misma escritura a ciegas (una creación puede haber tenido éxito parcial — comprueba primero con un
GET).
Siempre devuelve el message de la API al usuario en lugar de reintentar a ciegas.
El contrato completo y autoritativo de solicitud/respuesta es la interfaz Swagger en vivo en https://api.fiz.co/ — consúltalo si algún campo aquí parece desactualizado.
Cuando necesitas más detalle
Dos archivos complementarios en este directorio del skill:
domain.md— el dominio fiscal portugués: tipos de IVA por territorio, la lista completa de códigos de exención (M) y cuándo se aplica cada uno, tipos de documento, reglas de CAE/NIF, retención en la fuente y cómo funcionan la sincronización con AT y la irreversibilidad de la emisión. Léelo siempre que una decisión fiscal no sea obvia (especialmente al elegir unvatRateo un motivo de exención).reference.md— la referencia completa de la API campo por campo (todos los campos de cliente/artículo/factura, todos los valores de enumeración, formas completas de solicitud/respuesta).
Apéndice — curl sin formato (sin el script auxiliar)
Prefiere el script auxiliar fiz anterior; comprueba el código de estado HTTP. Si no está disponible, usa curl directamente con -w '\n%{http_code}' y parsea la última línea. Ejemplos:
# Buscar clientes
resp=$(curl -s -w '\n%{http_code}' -H "x-api-key: $FIZ_API_KEY" \
"$FIZ_API_URL/customers?search=Joao")
http_code=$(echo "$resp" | tail -1)
body=$(echo "$resp" | sed '$d')
if [ "$http_code" -ge 400 ]; then
echo "Error $http_code: $body"
fi
# Crear cliente
curl -s -X POST -w '\nHTTP %{http_code}' \
-H "x-api-key: $FIZ_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "name": "João Silva" }' \
"$FIZ_API_URL/customers"
Siempre supone que la clave API está en una variable de entorno, nunca la codifiques directamente.
Este skill es mantenido por FIZ — facturación portuguesa para desarrolladores. Documentación completa: https://docs.fiz.co