Plan de pruebas de preproducción para una API de contabilidad
Un manual de confiabilidad para equipos de ingeniería: escrituras duplicadas, reutilización de idempotencia, firmas de webhooks, eventos desordenados, importes decimales, ámbitos (scopes), periodos bloqueados y recuperación.
Un código 201 Created de una API de contabilidad no dice prácticamente nada. Solo confirma que el servidor respondió. No confirma que exista exactamente una factura, que los débitos sean iguales a los créditos, que el número de documento se haya asignado una sola vez, o que un reintento tras una conexión caída no contabilice una segunda copia. La mayoría de los duplicados en la contabilidad de producción provienen de esa brecha: una mutación que se confirmó en el servidor mientras el cliente se quedó con un resultado desconocido y reintentó silenciosamente con una nueva clave.
Este plan está dirigido a los equipos que evalúan una API de contabilidad antes de integrarla en libros reales. Se centra en el comportamiento ante fallos, no en los casos ideales. Cada sección a continuación detalla qué probar, qué verificar y qué se considera superado. El contexto de implementación aquí es Nordlet, cuya API utiliza claves de portador delimitadas (scoped bearer keys), importes en cadenas de texto decimales (decimal-string amounts), claves de idempotencia y webhooks firmados. Para conocer la sintaxis exacta de los puntos de acceso (endpoints) y los contratos de respuesta, lea las páginas de Convenciones de la API y Primeros pasos en lugar de confiar en suposiciones.
Qué significa realmente "superar" la prueba
Una prueba demuestra que una integración contable es segura únicamente cuando verifica la semántica, no los códigos de estado. Para cada escenario, el conjunto de pruebas debe confirmar:
- Efecto secundario de negocio: existe exactamente un cliente, factura, asiento contable, asignación de números o transición de pago.
- Integridad contable: los débitos son iguales a los créditos y la precisión monetaria sobrevive a la serialización.
- Comportamiento de reproducción: un reintento devuelve el resultado original en lugar de un nuevo registro.
- Durabilidad: los registros de auditoría y los eventos de webhook existen una vez que la operación se confirma.
- Fallos sin efectos secundarios: las solicitudes rechazadas no consumen números de documento ni escriben líneas de libro mayor parciales.
- Recuperación: un error transitorio se puede reintentar sin duplicación ni pérdida permanente.
Las directrices de Google sobre API trazan el límite con claridad: solo las operaciones cuya ejecución repetida deja el mismo estado final son seguras para reintentar, y una API debe identificar qué operaciones son idempotentes en lugar de reintentar ciegamente cada fallo.
Configurar un entorno observable y desechable
Utilice una empresa de pruebas (sandbox), no los libros de producción. Una empresa de pruebas en Nordlet utiliza los mismos módulos y el mismo comportamiento de API que una empresa real, y está etiquetada como datos de prueba, lo que la hace segura para pruebas de alto volumen de duplicados y fallos. La marca es inmutable tras su creación, por lo que los libros reales no pueden volver a clasificarse como datos de prueba más adelante. Tenga en cuenta que el uso de la empresa de pruebas se mide y se factura exactamente igual que el uso en una empresa real, así que ajuste el volumen de sus pruebas al precio que tenga contratado.
Antes de escribir cualquier prueba, prepare:
- Una empresa de pruebas dedicada con
isSandbox: true. - Una clave de API de prueba limitada únicamente a los módulos bajo prueba.
- Un punto de recepción de webhooks (webhook endpoint) o túnel local que conserve el cuerpo de la solicitud en bruto (raw request body).
- Clientes, proveedores y un plan de cuentas (chart of accounts) conocidos y precargados.
- Al menos dos periodos contables: uno abierto y uno bloqueado.
- Un reloj de prueba o fecha fija si la implementación lo admite.
- Un almacenamiento local que registre cada intento de solicitud, clave de idempotencia, hash de respuesta, entrega de webhook y resultado de conciliación.
No ejecute pruebas de duplicados en datos de ensayo compartidos (staging data). Un cliente o factura extraviados de una ejecución anterior hacen que las verificaciones posteriores sean ambiguas.
Su elemento de prueba (fixture) para cada mutación debe capturar el método, el punto de acceso, el cuerpo completo, la clave de idempotencia, el ámbito de la clave, un identificador de correlación del cliente, las marcas de tiempo de inicio y finalización, el estado de la respuesta, las cabeceras (headers), el cuerpo, el identificador de solicitud y si la conexión se cayó antes de que llegara la respuesta. Utilice cadenas decimales fijas como "75.0000" para el dinero. Nordlet representa los valores monetarios como cadenas decimales con hasta cuatro decimales, y su factura de ejemplo utiliza ese formato.
La otra mitad de la configuración es la inyección de fallos. Su entorno de pruebas debe ser capaz de retrasar una respuesta, interrumpir una conexión TCP después de que el servidor haya recibido la solicitud, devolver respuestas sintéticas 429/500/502/503/504, devolver un código distinto de 2xx desde el receptor de webhooks, entregar el mismo webhook dos veces, alterar el orden de los eventos, corromper un byte del cuerpo de un webhook, cambiar la carga útil (payload) de una clave de idempotencia manteniendo la clave, ejecutar dos solicitudes idénticas de forma concurrente y bloquear un periodo entre la configuración y la contabilización. Estos escenarios valen más que cien ejemplos de casos ideales.
Fijar el contrato antes de escribir la lógica de reintentos
Genere u obtenga el esquema OpenAPI y compárelo con su cliente. Nordlet publica su especificación OpenAPI en la referencia de la API y genera sus SDK tipados a partir de esa misma especificación, por lo que las pruebas basadas en esquemas superan a las suposiciones escritas a mano.
Verifique los campos y tipos obligatorios, los campos de cadena decimal, los formatos de fecha y marca de tiempo, las acciones y ámbitos permitidos, la estructura del contenedor de errores (error envelope), los esquemas de eventos de webhook, qué mutaciones aceptan una clave de idempotencia y qué campos de respuesta identifican el objeto creado.
Criterios de superación. El cliente rechaza localmente las cargas útiles inválidas siempre que el esquema lo permita. Las respuestas del servidor coinciden con el esquema documentado. Los decimales no válidos, las fechas incorrectas y los campos obligatorios faltantes producen errores de validación estructurados. No se intenta ningún reintento ante un fallo de validación determinista.
Modo de fallo a detectar. Un cliente analiza "121.0000" convirtiéndolo en un número de punto flotante binario y serializa un importe ligeramente diferente más adelante. Los números enteros pasan; las líneas de IVA y las cantidades fraccionarias fallan. Pruebe los valores monetarios como cadenas de texto de extremo a extremo.
Autorización y aislamiento de inquilinos (tenants)
Utilice claves separadas para lectura y escritura. A continuación, pruebe toda la matriz: clave faltante, clave no válida, clave válida con ámbito de módulo insuficiente, clave válida con el ámbito correcto, clave perteneciente a otra empresa e intento de leer un objeto de una empresa diferente.
Verificaciones:
- Las credenciales faltantes o no válidas devuelven
401. - Una clave válida sin el permiso requerido devuelve
403. - No se crea ningún efecto secundario contable ante ninguno de los dos fallos.
- Una solicitud exitosa no puede leer ni alterar los registros de otra empresa.
- Los fallos de seguridad no se reintentan nunca de forma automática.
Criterio de superación. Puede demostrar tanto la corrección de la autorización como el aislamiento de inquilinos mediante lecturas posteriores a la prueba, no solo con códigos de estado.
Demostrar una transacción limpia
Ejecute un flujo pequeño y determinista: cree o busque un cliente de prueba, cree una factura con importes en cadenas decimales, emítala, lea la factura y su efecto contable, lea el registro de auditoría y reciba el webhook. El ejemplo de primeros pasos de Nordlet muestra que la emisión de facturas asigna un número continuo (gapless number), comprueba que el periodo está abierto y contabiliza un asiento equilibrado en la misma transacción.
Confirme que la factura alcanza el estado esperado, que el número de documento se asigna exactamente una vez, que los débitos son iguales a los créditos, que el IVA y los totales de las líneas coinciden con el cálculo esperado de redondeo hacia arriba (half-up), que el registro de auditoría menciona al actor y al cambio, que el webhook se entrega eventualmente y que la respuesta de la API y una lectura posterior coinciden en el identificador y el estado de la factura.
Una distinción que vale la pena mantener: la llegada de un webhook no es prueba de que la mutación se haya confirmado. La respuesta de la API junto con una lectura posterior son sus principales verificaciones de transacciones. El webhook demuestra la notificación posterior y se prueba por separado.
Idempotencia: misma clave, mismo cuerpo
Genere una clave por operación de negocio lógica, no una clave por intento de red. Envíe la mutación con la clave K y el cuerpo B, guarde el identificador creado y, a continuación, envíe exactamente la misma solicitud de nuevo.
Confirme que la segunda respuesta representa la misma operación, que el identificador del objeto es idéntico y que existe un solo objeto de negocio, un solo asiento, un solo número de documento y un solo efecto financiero. La respuesta reproducida debe incluir la cabecera de reproducción documentada (x-idempotent-replay: true en el caso de Nordlet) y el rastro de auditoría no debe mostrar ninguna segunda mutación.
Dos reglas de almacenamiento modifican lo que su bucle de reintentos debe esperar, así que pruébelas directamente: un error 4xx almacenado se reproduce como cualquier otra respuesta, mientras que un error 5xx no se almacena en absoluto, por lo que un reintento tras un error del servidor vuelve a ejecutar la operación en lugar de reproducirla. Las claves también caducan (en Nordlet tras 24 horas), lo que limita el tiempo durante el cual hay una reproducción disponible.
La documentación de Nordlet promete que añadir una cabecera Idempotency-Key evita que una solicitud reintentada cree un duplicado, y sus convenciones de API definen la cabecera de reproducción exacta y el requisito de respuesta byte por byte. El principio coincide con el modelo publicado por Stripe: una clave identifica la operación lógica, las repeticiones devuelven el resultado almacenado y una clave reutilizada con parámetros diferentes es rechazada en lugar de modificada silenciosamente.
Criterio de superación. Existe exactamente una transición de estado financiero sin importar cuántas veces envíe el cliente la misma solicitud dentro de la ventana de reproducción.
Idempotencia: misma clave, cuerpo modificado
Envíe la solicitud original con la clave K y el cuerpo B1, y luego una segunda solicitud con la clave K y el cuerpo B2, donde al menos un importe, cliente, fecha o línea difiera.
Resultado esperado: la solicitud se rechaza con el error documentado de reutilización de claves (Nordlet devuelve 422 idempotency_key_reuse), el registro original no se modifica, no aparece ningún segundo registro, número o mutación de auditoría, y el cliente no lo "soluciona" generando una nueva clave. Aceptar silenciosamente B2 haría que las claves de reintento no fueran seguras.
Solicitudes duplicadas concurrentes
Lanze dos solicitudes idénticas con la misma clave en el mismo instante. Dependiendo del comportamiento documentado, una se ejecuta mientras la otra recibe un conflicto de en curso (Nordlet devuelve 409 idempotency_in_progress) o ambas observan eventualmente el mismo resultado.
Confirme que ambas llamadas no pueden crear efectos financieros independientes, que como máximo se asigna un número de documento, que un conflicto temporal se reintenta con la misma clave en lugar de una nueva, que el recurso final está completo y es legible, y que el registro de idempotencia no se queda bloqueado permanentemente.
Modo de fallo. Una biblioteca de reintentos que genera un UUID nuevo en cada intento convierte una operación lógica en varias escrituras independientes y arruina por completo la idempotencia.
El resultado incierto tras una conexión caída
Esta es la simulación de producción más importante de todas. Envíe una mutación con la clave K, permita que el servidor la confirme, interrumpa la conexión antes de que llegue la respuesta, registre el resultado como desconocido, reintente con la misma clave K y concilie leyendo el recurso y los datos de auditoría.
Confirme que el reintento devuelve la respuesta original o la respuesta de reproducción documentada, que existe exactamente un efecto secundario financiero, que el cliente marca la operación como completa solo tras una reproducción exitosa o una lectura de conciliación, y que no se genera ninguna clave nueva. Un tiempo de espera agotado (timeout) no significa que "no haya pasado nada". Stripe describe la idempotencia precisamente como protección frente a errores de conexión en los que el servidor puede haber procesado una mutación antes de que el cliente pudiera recibir la respuesta.
Una política de reintentos conservadora
Las decisiones de reintento dependen tanto del error como de si la operación es idempotente, no únicamente del método HTTP.
Normalmente reintentable, se conserva la misma clave: restablecimiento de conexión o tiempo de espera agotado tras una mutación idempotente, 408, 429 tras respetar retry-after, y 502/503/504. Una respuesta documentada de idempotencia en curso se puede reintentar tras una breve pausa.
No reintentable sin conciliación: errores de validación 400/422, fallos de autenticación 401/403, 404 por recurso faltante, conflictos de negocio 409 no relacionados con una clave en curso, carga útil modificada en una clave existente y rechazo por periodo bloqueado.
Confirme un retraso exponencial acotado con fluctuación (jitter), un número máximo explícito de intentos y tiempo transcurrido, el respeto a retry-after para los límites de velocidad (rate limits), la misma clave mantenida en los reintentos de una operación, una clave nueva para cada operación nueva y registros que separen el intento original de los reintentos.
Pruebe la limitación de velocidad de manera aislada. Supere el umbral documentado (Nordlet permite por defecto 300 solicitudes por minuto por clave de API, y algunos puntos de acceso sin autenticar tienen sus propios límites inferiores por dirección) y confirme que el servidor devuelve la respuesta de límite de velocidad, que el cliente interpreta retry-after como una pausa en lugar de un error, que se detiene en lugar de saturar con solicitudes y que el reintento de una mutación limitada reutiliza la clave original. Las cabeceras x-ratelimit-* informan del presupuesto restante, lo cual sirve como una buena verificación previa. Mantenga las pruebas de límite de velocidad alejadas de las afirmaciones financieras a menos que su entorno de pruebas pueda distinguir un intento limitado de uno confirmado.
Verificación de firmas de webhooks
Construya un receptor que conserve los bytes en bruto. Para cada evento: capture el cuerpo en bruto, lea la cabecera x-nordlet-signature, calcule el HMAC-SHA256 esperado con el secreto de suscripción, compare utilizando una función de tiempo constante (constant-time function), analice el JSON solo después de la verificación, registre la entrega y devuelva un código 2xx rápido tras la aceptación duradera. Nordlet firma un HMAC sobre el cuerpo en bruto, lo envía como sha256=<hex> y reintenta las entregas con un retraso exponencial. Las directrices sobre webhooks de Stripe refuerzan la verificación del cuerpo en bruto sin modificar mediante una comparación de tiempo constante.
Ejecute pruebas negativas: cuerpo y secreto correctos, cuerpo correcto con secreto erróneo, cambio de un byte en el cuerpo, JSON reserializado con espacios en blanco alterados, cabecera faltante, firma malformada, reproducción de una entrega antigua si el esquema incluye una marca de tiempo, y firma válida entregada a la suscripción incorrecta.
Criterios de superación. Solo se aceptan el cuerpo en bruto y el secreto correctos. Las firmas no válidas obtienen un código distinto de 2xx. Las cargas útiles no válidas nunca entran en la cola contable. Los secretos se mantienen fuera de los registros habituales. El manejador envía una confirmación únicamente después de que el evento se haya registrado de forma duradera.
Webhooks duplicados y desordenados
La confiabilidad de la entrega es un problema distinto de la idempotencia de la API, y el receptor también debe ser idempotente. Entregue el mismo evento dos veces, entregue un evento sale_invoice.paid antes de un evento sale_invoice.issued, devuelva un error 500 tras persistir pero antes de responder, y reproduzca un evento que el consumidor ya haya procesado.
Confirme que las entregas duplicadas no duplican pagos, cambios de estado o acciones de asientos, que la clave de deduplicación utiliza la entrega del proveedor o el identificador de evento cuando existe (o una clave compuesta documentada, nunca una marca de tiempo arbitraria), que los eventos desordenados nunca sobrescriben un estado más reciente con uno más antiguo, que el consumidor puede obtener el estado actual desde la API cuando el orden no es suficiente y que los intentos fallidos del consumidor se dirigen a través de una cola o ruta de mensajes muertos (dead-letter). Stripe advierte explícitamente que no se garantiza el orden de entrega y recomienda almacenar los identificadores de eventos procesados.
Mantenga el receptor ligero: valide la estructura, verifique la firma, persista o encole, confirme y devuelva un código 2xx. La conciliación y las notificaciones corresponden al procesamiento asíncrono. Pruébelo haciendo que el trabajo posterior espere más allá del tiempo de espera de entrega; un receptor correcto sigue respondiendo tras el encolamiento duradero.
Periodos contables abiertos y bloqueados
Nordlet aplica bloqueos de periodo en el momento de la contabilización en diarios manuales, flujos de documentos e importaciones, así que pruebe este control en más de un lugar. Cree documentos equivalentes con fecha en un periodo abierto, en un periodo bloqueado, en el límite justo antes del bloqueo, en el primer día del siguiente periodo abierto y con una fecha de documento válida pero una fecha de contabilización no válida si la API separa ambas.
Para el caso del periodo bloqueado, confirme que la solicitud se rechaza con el error de bloqueo de periodo documentado (un conflicto 409 en Nordlet) y que no se escriben líneas de asiento, no se consume ningún número de documento, ningún documento pasa al estado contabilizado y no se dispara ningún webhook de éxito. La versión de periodo abierto debería completarse con los mismos valores financieros.
Prueba de carrera (Race test). Inicie una contabilización en un periodo abierto, bloquee el periodo a mitad de la solicitud y confirme que la confirmación no puede saltarse el bloqueo. La condición crítica de superación: una contabilización confirmada nunca debe recaer en un periodo que se bloqueó antes de que la transacción se confirmara.
Auditoría y conciliación
Para cada mutación exitosa, confirme que existe el objeto de negocio, que existe el asiento contable con débitos iguales a créditos, que existe el registro de auditoría, que el evento de webhook existe o se puede recuperar, y que su registro de operación apunta al identificador de objeto del proveedor con reintentos que hacen referencia a la misma operación lógica.
Para cada mutación rechazada, confirme que no existe ningún documento parcial, que no se consumió ningún número a menos que el contrato permita la reserva, que no existe ninguna entrada en el libro mayor, que no existe ningún webhook de éxito y que el error incluye un identificador de solicitud para el diagnóstico. Las convenciones de Nordlet describen la auditabilidad de las mutaciones y las contabilizaciones equilibradas como garantías de la plataforma, con el equilibrio aplicado por un disparador de base de datos diferido (deferred database trigger) en el momento de la confirmación en lugar de solo por el código de aplicación. Convierta esas garantías en consultas de conciliación posteriores a las pruebas en lugar de confiar en la respuesta HTTP.
Matriz de pruebas principal
| Área | Acción de prueba | Verificación esperada | Criterio de superación |
|---|---|---|---|
| Autenticación | Sin clave, clave no válida, ámbito insuficiente | 401/403 estructurado; sin efecto secundario |
Ningún intento rechazado crea un registro contable |
| Esquema y dinero | Decimal no válido, fecha, campo obligatorio | Error de validación a nivel de campo | No se producen reintentos ni escrituras parciales |
| Contabilización normal | Crear y emitir una factura en un periodo abierto | Un documento, un asiento equilibrado, un registro de auditoría | El estado de lectura y el libro mayor coinciden |
| Reproducción con misma clave | Repetir mutación idéntica, misma clave y cuerpo | Resultado original reproducido; sin duplicados | El ID de objeto, número, efecto de asiento y auditoría se mantienen únicos |
| Cuerpo modificado, misma clave | Reutilizar clave con un importe diferente | Rechazo documentado por reutilización de clave | Registro original sin cambios; no se escribe nada nuevo |
| Conexión caída | Confirmar, interrumpir, reintentar con misma clave | Resultado reproducido | Exactamente un efecto secundario financiero |
| Periodo bloqueado | Contabilizar un documento fechado en periodo bloqueado | Rechazo por periodo bloqueado | Sin asiento, sin número, sin webhook de éxito |
| Firma de webhook | Secreto erróneo, cuerpo alterado | Código distinto de 2xx del receptor | Solo se aceptan el cuerpo y el secreto en bruto correctos |
Qué haría primero
Si el tiempo apremia, ejecute las pruebas en el orden que permita detectar los errores más costosos. Comience con el reintento por conexión caída, ya que ese único escenario genera la mayoría de los duplicados reales. A continuación, el par de idempotencia de misma clave y cuerpo modificado. Después, los periodos bloqueados, ya que una contabilización en un mes cerrado es un problema de auditoría que sale a la luz tarde. La verificación de firmas y los webhooks desordenados vienen después, y la matriz completa de esquemas y autorización puede ejecutarse de forma continua en integración continua (CI) una vez que el cliente se estabilice.
Todo lo demás en este plan respalda a estos cinco puntos. Consiga que pasen verde frente a una empresa de pruebas, con lecturas de conciliación que confirmen los efectos secundarios, y sabrá que la integración es segura antes de tocar un solo libro contable real. Puede crear una empresa de pruebas y una clave de prueba con ámbito en pocos minutos desde Primeros pasos, y comprobar qué módulos necesita en la lista de características.
FAQ
¿Es suficiente una respuesta 2xx para confirmar que una escritura contable tuvo éxito?
No. Un código de estado confirma que el servidor respondió, no que existan exactamente un documento, una asignación de número y un asiento contable equilibrado. Cada prueba de mutación debe ir seguida de una lectura de comprobación del objeto, su efecto contable y su registro de auditoría. El caso peligroso se da en la dirección opuesta: una operación que se confirmó mientras el cliente nunca recibió la respuesta.
¿Debo generar una nueva clave de idempotencia cuando una solicitud agota el tiempo de espera?
No, y este es el error que provoca la mayoría de los duplicados en producción. La clave identifica la operación de negocio lógica, no el intento de red. Reintente con la misma clave y luego concilie leyendo el recurso. Una biblioteca de reintentos que genera un UUID nuevo en cada intento convierte una factura prevista en varias escrituras independientes.
¿Qué pasa si reutilizo una clave de idempotencia con una carga útil diferente?
Una API correcta la rechaza en lugar de aplicar silenciosamente el nuevo cuerpo. Nordlet devuelve 422 idempotency_key_reuse y deja el registro original intacto. Su cliente debe tratar esto como un error de programación que debe corregir, no como una condición que deba resolver generando una nueva clave.
¿Son los webhooks un sustituto de la lectura posterior del objeto creado?
No. La entrega de webhooks demuestra la notificación posterior, no que la transacción se haya confirmado tal como usted espera, y no se garantiza el orden de entrega. Verifique la firma sobre el cuerpo en bruto, deduplique por el identificador de evento o entrega, y obtenga el estado actual de la API siempre que el orden de los eventos por sí solo no sea suficiente para decidir qué ha cambiado.
¿Cómo pruebo la contabilización en un periodo contable bloqueado?
Cree documentos equivalentes en un periodo abierto y en uno bloqueado, y verifique que el caso bloqueado se rechaza sin líneas de asiento, sin número de documento consumido, sin transición de estado y sin webhook de éxito. A continuación, ejecute la versión de carrera: inicie una contabilización en un periodo abierto y bloquee el periodo a mitad de la solicitud. Una contabilización confirmada nunca debe recaer en un periodo que se bloqueó antes de que la transacción se confirmara.
¿Puedo ejecutar pruebas de duplicados de alto volumen en una empresa de pruebas?
Sí. Una empresa de pruebas se comporta como una real, está etiquetada como datos de prueba y se elimina de inmediato en lugar de hacerlo tras el periodo de retención, por lo que es el lugar adecuado para realizar pruebas destructivas y de duplicados. El uso de la empresa de pruebas se mide y se factura como el uso real, así que planifique el volumen en lugar de ejecutar bucles indefinidamente.