Paquetes de extensión
Algunas integraciones son productos en sí mismos — feeds bancarios con credenciales de proveedor, facturación electrónica frente a APIs gubernamentales. REDA Financials estructura estas integraciones como paquetes de extensión: el núcleo define un contrato abstracto global, la extensión lo implementa y un registro de configuración conecta ambos. Hoy se distribuyen dos contratos base, más tres interfaces de capacidad opcionales que un proveedor implementa solo si las necesita.
Cómo funciona el patrón
Ambos contratos siguen la misma arquitectura, y los dos encabezados de clase lo describen con las mismas palabras: "The core package resolves the provider at runtime via: Type.forName(setup.Provider_Class__c).newInstance()". Ningún contrato codifica un proveedor de forma fija. Hay cuatro piezas móviles, cada una con un dueño claro:
| Pieza | Dueño | Rol |
|---|---|---|
Contrato abstracto — refin.BankFeedProvider / refin.EInvoiceProvider | Paquete núcleo | Clases abstractas globales (ambas marcadas @since 2026-05-28) que definen las firmas de método y las formas de resultado. Global = parte de la superficie estable del paquete gestionado. |
| Implementación | Paquete de extensión (o tu org) | Una clase concreta que extiende el contrato; contiene toda la lógica de API específica del proveedor. Según ambas documentaciones de clase: "Authentication and API credentials are managed entirely by the extension package." |
Registro de configuración — refin__Bank_Feed_Setup__c / refin__E_Invoice_Setup__c | Tu administrador | Registra la clase implementadora con nombre completo en Provider_Class__c, además del entorno, la activación y la configuración de alcance. |
Objeto de resultado — BankFeedResult / EInvoiceResult | Paquete núcleo | La clase interna global que devuelve cada método, de modo que los resultados se registran de manera uniforme sin importar el proveedor. |
refin.BankFeedProvider — extractos bancarios automatizados
- El contrato: tres métodos que aporta una implementación —
connect(el flujo de vinculación del proveedor),sync(recuperar transacciones desde el cursor y entregar líneas de extracto),disconnect(revocar el acceso). Los resultados se devuelven en una forma estándar (éxito, transacciones importadas, cursor actualizado, saldo, error). - La conexión: un registro de Configuración de feed bancario por cada cuenta conectada — nombre del proveedor, entorno (Sandbox/Producción), el nombre de la clase implementadora, cursor y estado de sincronización (Active / Paused / Error / Disconnected), última sincronización. Las configuraciones viven en la página de registro de la cuenta bancaria.
- Las credenciales permanecen en la extensión — el núcleo nunca ve los tokens del proveedor; la extensión gestiona su autenticación de principio a fin.
- El resultado: los extractos llegan como extractos bancarios comunes (con el origen de importación marcado en consecuencia) y fluyen hacia el mismo emparejamiento y conciliación que tendría un CSV — automatización aguas arriba, flujo de trabajo conocido aguas abajo.
El contrato del feed bancario, método por método
Cada método recibe el registro de configuración como contexto. La documentación de la clase describe el modelo: "Each method receives the Bank_Feed_Setup__c record as the primary context — the provider reads the linked bank account and its own credentials to execute sync logic."
global abstract BankFeedResult sync(refin__Bank_Feed_Setup__c setup);
global abstract BankFeedResult connect(
refin__Bank_Feed_Setup__c setup,
Map<String, String> params
);
global abstract void disconnect(refin__Bank_Feed_Setup__c setup);
| Método | Qué exige el contrato de él (según la documentación de la clase) |
|---|---|
sync | "The provider fetches new transactions from the bank API (using the sync cursor from the setup record), converts them to Bank Statement Lines via the core package's internal import path, and returns the updated cursor for persistence." La documentación agrega que es "Called by the core scheduled batch job" — ver la salvedad de orquestación anterior; el lado de la programación depende de la versión del núcleo. |
connect | Inicia el flujo de vinculación de una nueva cuenta bancaria. "Provider-specific: may involve OAuth redirect, token exchange, or API key validation. The params map carries provider-specific connection parameters (e.g. public_token for Plaid, link_id for Belvo)." |
disconnect | "Revokes access tokens, removes webhooks, and cleans up provider-side resources. The core package updates the setup record's Sync_Status to Disconnected after this method completes." |
BankFeedResult es una clase interna global — cada miembro es @AuraEnabled global, y el constructor establece por defecto success en false y transactionsImported en 0, de modo que una implementación debe ganarse un éxito:
| Miembro | Tipo | Significado |
|---|---|---|
success | Boolean | Si la operación se completó. Por defecto, false. |
transactionsImported | Integer | Cuántas transacciones entregó la sincronización. Por defecto, 0. |
updatedCursor | String | La nueva posición incremental, devuelta "for persistence" para que la siguiente sincronización retome donde terminó esta. |
currentBalance | Decimal | El saldo de la cuenta reportado por el proveedor al momento de la sincronización. |
errorMessage | String | Detalle honesto del fallo cuando success es false. |
refin__Bank_Feed_Setup__c — referencia de campos
| Campo | Qué contiene |
|---|---|
Bank_Account__c | La cuenta bancaria de REDA a la que sirve este feed — una configuración por cuenta conectada. |
Provider_Class__c (etiqueta "Provider Class") | El corazón de la conexión. Descripción textual: "Fully qualified Apex class name that extends refin.BankFeedProvider… Extension packages register their implementation here (e.g. plaid.PlaidBankFeedProvider)." |
Provider__c | Solo nombre para mostrar — según su propia descripción, "The actual integration logic is determined by Provider_Class__c, not this field." |
Environment__c | A qué entorno del proveedor apunta la conexión (Sandbox / Producción). |
Is_Active__c | Si el feed participa en la sincronización. |
Sync_Cursor__c | La posición incremental del proveedor — sync la lee y devuelve el valor actualizado en BankFeedResult.updatedCursor. |
Sync_Status__c | Lista de selección: Active / Paused / Error / Disconnected. La ayuda del valor Error es explícita: "Error: last sync failed — check Audit Log for details." |
Last_Sync__c | Cuándo se sincronizó el feed por última vez. |
Provider_Account_Id__c | El identificador del lado del proveedor para la cuenta vinculada. |
Un esqueleto de implementación
Así es la forma de un proveedor — el equipo de Meridian esbozando uno contra la API directa de su banco estadounidense. Solo las piezas del contrato son reales; los comentarios marcan dónde va tu lógica de proveedor:
global class MeridianBankFeedProvider extends refin.BankFeedProvider {
global override refin.BankFeedProvider.BankFeedResult connect(
refin__Bank_Feed_Setup__c setup, Map<String, String> params) {
refin.BankFeedProvider.BankFeedResult result =
new refin.BankFeedProvider.BankFeedResult();
// Intercambia los parámetros de conexión (p. ej. public_token) con el
// proveedor. Guarda las credenciales en TU paquete — nunca en objetos del núcleo.
result.success = true;
return result;
}
global override refin.BankFeedProvider.BankFeedResult sync(
refin__Bank_Feed_Setup__c setup) {
refin.BankFeedProvider.BankFeedResult result =
new refin.BankFeedProvider.BankFeedResult();
// 1. Recupera las transacciones desde setup.refin__Sync_Cursor__c
// 2. Entrégalas como líneas de extracto bancario
// 3. Devuelve el nuevo cursor para que pueda persistirse
result.transactionsImported = 42;
result.updatedCursor = 'cursor-after-this-batch';
result.success = true;
return result;
}
global override void disconnect(refin__Bank_Feed_Setup__c setup) {
// Revoca tokens y webhooks del lado del proveedor; el núcleo
// establece Sync_Status en Disconnected después de que esto se complete.
}
}
refin.EInvoiceProvider — facturación electrónica estatutaria
- El contrato:
submit(construir la carga útil de la jurisdicción a partir del documento y llamar a la autoridad),checkStatus(consultar autoridades asíncronas),cancel(aplican reglas específicas de la jurisdicción — la propia nota del contrato cita la ventana de 24 horas de Brasil y la aceptación del receptor en México). - La conexión: una Configuración de facturación electrónica por entidad/país — proveedor, entorno, clase implementadora, qué tipos de documento participan y una opción Auto Submit On Post; cada intento de envío se registra como un Historial de facturación electrónica en el documento (estado, UUID y número fiscal, cargas útiles, respuesta, cantidad de reintentos) — el rastro de auditoría de cumplimiento que exigen los reguladores.
- Nota de disponibilidad: los historiales y las listas relacionadas son visibles en toda la aplicación (ya has visto Historiales de facturación electrónica en las páginas de facturas); el envío en vivo requiere la extensión de proveedor de tu jurisdicción — confirma con REDA qué está disponible para tu país.
El contrato de facturación electrónica, método por método
| Método | Firma | Contrato |
|---|---|---|
| submit | global abstract EInvoiceResult submit(refin__E_Invoice_Trail__c trail, SObject sourceDocument) | El documento de origen es un refin__Sales_Invoice__c o refin__Purchase_Invoice__c; el proveedor construye el XML/JSON de la jurisdicción y llama a la API gubernamental; el núcleo actualiza el Trail a partir del resultado |
| checkStatus | global abstract EInvoiceResult checkStatus(refin__E_Invoice_Trail__c trail) | Consulta para autoridades asíncronas que responden más tarde |
| cancel | global abstract EInvoiceResult cancel(refin__E_Invoice_Trail__c trail) | "Jurisdiction-specific rules apply (e.g. Brazil NF-e cancellation within 24h, Mexico CFDI cancellation with receiver acceptance)." |
Los tres métodos devuelven un EInvoiceResult — miembros: success, status, fiscalUUID, fiscalNumber, responseCode, responseMessage, xmlPayload, responsePayload, errorMessage.
Los objetos de facturación electrónica — referencia de campos
Campo de refin__E_Invoice_Setup__c | Rol |
|---|---|
Entity__c · Country_Code__c · Environment__c · Is_Active__c | Qué empresa, qué jurisdicción, prueba vs. producción, activado/desactivado |
Provider__c | Solo nombre para mostrar |
Provider_Class__c | La clase Apex con nombre completo que extiende refin.EInvoiceProvider (el ejemplo del propio campo: sovos.SovosEInvoiceProvider) |
Document_Types__c | Selección múltiple: Sales Invoice / Credit Memo / Purchase Invoice / Vendor Memo — el "core package checks this field when determining whether to create an E-Invoice Trail record" |
Auto_Submit_On_Post__c | Textual: "When checked, posting a Sales Invoice or Purchase Invoice automatically creates an E-Invoice Trail record in Draft status, triggering the asynchronous submission process. When unchecked, e-invoice submission must be initiated manually via a quick action or API call." |
El registro refin__E_Invoice_Trail__c es la fila de auditoría por envío: búsquedas a la configuración y a la factura de origen, Document_Type__c, Status__c, Fiscal_UUID__c, Fiscal_Number__c, XML_Payload__c, los campos de respuesta (Response_Code__c/Message__c/Payload__c/Date__c), Submitted_Date__c, Retry_Count__c y Provider_Document_Id__c — la cadena de evidencia que esperan los reguladores, una fila por intento. Status__c tiene ocho valores: Draft, Ready to Send, Queued, Submitted, Accepted, Rejected, Cancelled, Error.
Interfaces de capacidad opcionales — cómo crece un contrato congelado
refin.EInvoiceProvider se publicó como clase abstracta global en una versión Managed-Released, lo que significa que nunca se le puede añadir un método. En lugar de congelar el marco con ella, REDA añade el comportamiento nuevo como interfaces globales independientes, algo que Salesforce sí permite. Un proveedor extiende EInvoiceProvider y implementa las interfaces de capacidad que admite; el núcleo detecta el soporte con instanceof y simplemente omite la capacidad para los proveedores que no la implementan.
| Interfaz | Métodos | Impleméntala cuando |
|---|---|---|
refin.EInvoiceQueueProvider | drain(setup, maxRecords) que devuelve List<EInvoiceEvent>; acknowledge(setup, events) | Tu autoridad fiscal responde mediante una cola de notificaciones y no con una respuesta síncrona. Un solo drenaje lleva ambas direcciones — actualizaciones de estado salientes y documentos de proveedor entrantes — diferenciadas por EInvoiceEvent.processType. Deliberadamente no hay un método aparte para "traer entrantes". |
refin.EInvoiceDocumentProvider | fetchDocuments(trail) que devuelve List<EInvoiceDocument> | Recuperas los artefactos legales (XML firmado, respuesta de la autoridad, una representación imprimible como el DANFE de Brasil) bajo demanda en lugar de entregarlos en línea con el evento. Omítela si tus eventos ya traen documents. |
refin.EInvoiceProvisioner | provisionOrganization(setup, args); provisionCompany(setup, args), ambos devuelven EInvoiceProvisionResult | Admites el aprovisionamiento autoservicio de las estructuras de cuenta del lado de la autoridad desde la propia aplicación. Es una capacidad de fase posterior: hoy esas estructuras las crea manualmente el proveedor de cumplimiento. Se declara ahora precisamente porque una API global ya publicada no se puede extender después. |
acknowledge únicamente cuando cada evento ya está almacenado de forma duradera; confirmar antes perdería la notificación para siempre. Un proveedor no debe activar ningún modo de confirmación automática que se adelante a esa llamada, y drain debe devolver solo notificaciones sin confirmar, para que los drenajes sucesivos avancen.
Los tres tipos de datos que intercambian
refin.EInvoiceEvent: un hito en la vida de un documento ante la autoridad. El núcleo lo correlaciona con un rastro medianteerpDocumentId, un valor que tu proveedor estampa en el sobre saliente y que la autoridad devuelve.statusse normaliza a Accepted, Rejected, Cancelled o InProgress: el núcleo basa los reintentos y los estados terminales en ese campo, nunca en elresponseCodecrudo. También llevanotificationId,processType(Outbound / Inbound),responseMessage,fiscalUUID,fiscalNumber, una lista opcionaldocuments,payloadyeventDate.refin.EInvoiceDocument: un artefacto, contitle,fileExtension('xml' o 'pdf'),base64BodyydocumentKind('Legal', 'LegalResponse' o 'Representation'). El núcleo lo decodifica y lo adjunta al rastro y a la factura de origen como archivos de Salesforce, para que los usuarios vean los artefactos conformes en el registro con el que ya trabajan.refin.EInvoiceProvisionResult:success(que por defecto esfalse),externalId(el identificador del lado del proveedor) ymessage.
documentKind, processType y status son todos String y no enumeraciones, deliberadamente, para que el conjunto permitido pueda crecer sin romper una firma global congelada. No hagas un switch exhaustivo sobre ellos: gestiona los valores que conoces y deja pasar el resto.
Errores comunes que evitar
| Error | Por qué perjudica | Haz esto en su lugar |
|---|---|---|
Errores de tipeo en Provider_Class__c | La resolución en tiempo de ejecución es por nombre — una cadena incorrecta significa que el proveedor simplemente nunca se ejecuta | Copia el nombre completo (namespace.ClassName) desde la documentación de la extensión; prueba la conexión de inmediato |
Confiar en Provider__c para cambiar de proveedor | Es una etiqueta de visualización; "The actual integration logic is determined by Provider_Class__c, not this field." | Cambia Provider_Class__c; actualiza la etiqueta para los humanos |
| Almacenar credenciales de autoridades/bancos en los registros de configuración | El contrato es explícito: "Authentication and API credentials are managed entirely by the extension package" | Las credenciales viven en las credenciales con nombre propias de la extensión, gobernadas por la seguridad de tu org |
| Construir el código de tu org contra los internos de un paquete de proveedor | Los paquetes de proveedor evolucionan según su propio calendario — solo los contratos globales refin. son la costura estable | Intégrate a través de los registros de configuración y los historiales; trata al proveedor como una caja negra |
| Asumir detalles de orquestación sin probar tu versión | El comportamiento de programación y despacho automático depende de la versión | Verifica la programación de sincronización y el comportamiento de envío automático en un sandbox de tus versiones instaladas antes de prometerlos al negocio |
Para equipos que construyen una extensión
- Implementa la clase abstracta en el namespace de tu paquete; registra el nombre de clase completo en el registro de configuración.
- Gestiona tus propias credenciales (credenciales con nombre en tu paquete), tu mapeo de API, tu semántica de errores — devuelve resultados honestos; el núcleo los expone en Sync Status y en los historiales.
- Construye solo contra el contrato — la superficie global documentada es la estable (las reglas).
Qué sigue
- Importación de extractos bancarios — el flujo donde aterrizan los feeds.
- Integraciones de AppExchange — el panorama más amplio del ecosistema.
- Desarrollo en Apex sobre REDA — las reglas de la superficie global.
Preguntas frecuentes
El contrato está diseñado para el ecosistema de agregadores (Plaid, Belvo y similares son los arquetipos que menciona). Consulta con REDA qué extensiones de proveedor están vigentes para tu región — la disponibilidad evoluciona más rápido que la documentación.
El contrato resuelve la clase implementadora por nombre, por lo que una implementación local al org es técnicamente direccionable — sensato para una API bancaria a medida. Empaquétala correctamente si va a sobrevivir a un solo org.
Depende del país (Brasil, México, Italia y Arabia Saudita lideran; muchos otros siguen). Tu asesor fiscal decide el "¿debemos?"; la configuración y la extensión del proveedor resuelven el "¿cómo?".
BankFeedResult informa success, transactionsImported, updatedCursor (dónde retoma la siguiente sincronización), currentBalance y errorMessage — los valores predeterminados son honestos (success=false, cero importado), de modo que un proveedor descuidado no pueda reportar éxito por accidente. EInvoiceResult transporta la respuesta de la autoridad: identificadores fiscales, cargas útiles de respuesta y el texto del error cuando rechazó la solicitud.
Es el estado de salud de la conexión de un vistazo — Active / Paused / Error / Disconnected. La ayuda del propio campo para el estado problemático: "Error: last sync failed — check Audit Log for details." Disconnected lo establece el núcleo después de que finaliza el disconnect de tu proveedor.
¿Te resultó útil?