@ssddo/ecf-react 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +252 -0
- package/dist/index.d.mts +6856 -0
- package/dist/index.d.ts +6856 -0
- package/dist/index.js +62 -0
- package/dist/index.mjs +25 -0
- package/package.json +49 -0
package/README.md
ADDED
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
# @ssddo/ecf-react
|
|
2
|
+
|
|
3
|
+
Hooks de React Query para la API de ECF DGII (comprobantes fiscales electrónicos de República Dominicana). Construido sobre `openapi-react-query` y `openapi-fetch` para interacciones con la API completamente tipadas.
|
|
4
|
+
|
|
5
|
+
## Instalación
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @ssddo/ecf-react @tanstack/react-query
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Configuración
|
|
12
|
+
|
|
13
|
+
Envuelve tu aplicación con `QueryClientProvider` y crea el cliente ECF:
|
|
14
|
+
|
|
15
|
+
```tsx
|
|
16
|
+
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
17
|
+
import { createEcfReactClient } from '@ssddo/ecf-react';
|
|
18
|
+
|
|
19
|
+
const queryClient = new QueryClient();
|
|
20
|
+
|
|
21
|
+
const { $api } = createEcfReactClient({
|
|
22
|
+
apiKey: 'tu-api-key',
|
|
23
|
+
environment: 'test', // 'test' | 'cert' | 'prod'
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
function App() {
|
|
27
|
+
return (
|
|
28
|
+
<QueryClientProvider client={queryClient}>
|
|
29
|
+
<TuApp />
|
|
30
|
+
</QueryClientProvider>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
También puedes proporcionar una URL base personalizada en lugar de usar un entorno predefinido:
|
|
36
|
+
|
|
37
|
+
```tsx
|
|
38
|
+
const { $api } = createEcfReactClient({
|
|
39
|
+
apiKey: 'tu-api-key',
|
|
40
|
+
baseUrl: 'https://api-personalizada.ejemplo.com',
|
|
41
|
+
});
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Uso
|
|
45
|
+
|
|
46
|
+
### Consultar datos
|
|
47
|
+
|
|
48
|
+
Usa el objeto `$api` para acceder a hooks tipados de React Query para cada endpoint:
|
|
49
|
+
|
|
50
|
+
```tsx
|
|
51
|
+
function Empresas() {
|
|
52
|
+
const { data, isLoading, error } = $api.useQuery('get', '/company', {
|
|
53
|
+
params: { query: { Page: 1, Limit: 10 } },
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
if (isLoading) return <div>Cargando...</div>;
|
|
57
|
+
if (error) return <div>Error: {error.message}</div>;
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<ul>
|
|
61
|
+
{data?.data?.map((company) => (
|
|
62
|
+
<li key={company.rnc}>{company.name}</li>
|
|
63
|
+
))}
|
|
64
|
+
</ul>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Buscar ECFs
|
|
70
|
+
|
|
71
|
+
```tsx
|
|
72
|
+
function BuscarEcf({ rnc }: { rnc: string }) {
|
|
73
|
+
const { data } = $api.useQuery('get', '/ecf/{rnc}', {
|
|
74
|
+
params: { path: { rnc } },
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
return <pre>{JSON.stringify(data, null, 2)}</pre>;
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Enviar ECFs (Mutaciones)
|
|
82
|
+
|
|
83
|
+
```tsx
|
|
84
|
+
function EnviarEcf() {
|
|
85
|
+
const mutation = $api.useMutation('post', '/ecf/31');
|
|
86
|
+
|
|
87
|
+
const handleSend = () => {
|
|
88
|
+
mutation.mutate({
|
|
89
|
+
body: {
|
|
90
|
+
Encabezado: {
|
|
91
|
+
IdDoc: {
|
|
92
|
+
ENCF: "E310000051630",
|
|
93
|
+
TipoeCF: "FacturaDeCreditoFiscalElectronica",
|
|
94
|
+
TipoPago: "Contado",
|
|
95
|
+
TipoIngresos: "01",
|
|
96
|
+
TablaFormasPago: [
|
|
97
|
+
{ FormaPago: "Efectivo", MontoPago: 1015.25 },
|
|
98
|
+
],
|
|
99
|
+
IndicadorMontoGravado: "ConITBISIncluido",
|
|
100
|
+
FechaVencimientoSecuencia: "2028-12-31T00:00:00",
|
|
101
|
+
},
|
|
102
|
+
Emisor: {
|
|
103
|
+
RNCEmisor: "131460941",
|
|
104
|
+
FechaEmision: "2026-01-10",
|
|
105
|
+
DireccionEmisor: "AVE. ISABEL AGUIAR NO. 269, ZONA INDUSTRIAL DE HERRERA",
|
|
106
|
+
RazonSocialEmisor: "DOCUMENTOS ELECTRONICOS DE 02",
|
|
107
|
+
},
|
|
108
|
+
Totales: {
|
|
109
|
+
ITBIS1: 18,
|
|
110
|
+
MontoGravadoI1: 762.71,
|
|
111
|
+
MontoGravadoTotal: 762.71,
|
|
112
|
+
TotalITBIS1: 137.29,
|
|
113
|
+
TotalITBIS: 137.29,
|
|
114
|
+
MontoNoFacturable: 100.0,
|
|
115
|
+
ImpuestosAdicionales: [
|
|
116
|
+
{
|
|
117
|
+
TipoImpuesto: "002",
|
|
118
|
+
TasaImpuestoAdicional: 2,
|
|
119
|
+
OtrosImpuestosAdicionales: 15.25,
|
|
120
|
+
},
|
|
121
|
+
],
|
|
122
|
+
MontoImpuestoAdicional: 15.25,
|
|
123
|
+
MontoTotal: 1015.25,
|
|
124
|
+
MontoPeriodo: 1015.25,
|
|
125
|
+
},
|
|
126
|
+
Version: "Version1_0",
|
|
127
|
+
Comprador: {
|
|
128
|
+
RNCComprador: "131880681",
|
|
129
|
+
RazonSocialComprador: "DOCUMENTOS ELECTRONICOS DE 03",
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
DetallesItems: [
|
|
133
|
+
{
|
|
134
|
+
MontoItem: 1016.95,
|
|
135
|
+
NombreItem: "Iphone 18 Pro max",
|
|
136
|
+
NumeroLinea: 1,
|
|
137
|
+
CantidadItem: 1,
|
|
138
|
+
UnidadMedida: "Unidad",
|
|
139
|
+
PrecioUnitarioItem: 1016.95,
|
|
140
|
+
IndicadorFacturacion: "ITBIS1_18Percent",
|
|
141
|
+
IndicadorBienoServicio: "Bien",
|
|
142
|
+
TablaImpuestoAdicional: [{ TipoImpuesto: "002" }],
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
MontoItem: 100.0,
|
|
146
|
+
NombreItem: "Costo de Envío",
|
|
147
|
+
NumeroLinea: 2,
|
|
148
|
+
CantidadItem: 1,
|
|
149
|
+
UnidadMedida: "Unidad",
|
|
150
|
+
PrecioUnitarioItem: 100.0,
|
|
151
|
+
IndicadorFacturacion: "NoFacturable_18Percent",
|
|
152
|
+
IndicadorBienoServicio: "Servicio",
|
|
153
|
+
},
|
|
154
|
+
],
|
|
155
|
+
DescuentosORecargos: [
|
|
156
|
+
{
|
|
157
|
+
TipoValor: "$",
|
|
158
|
+
TipoAjuste: "D",
|
|
159
|
+
NumeroLinea: 1,
|
|
160
|
+
MontoDescuentooRecargo: 84.75,
|
|
161
|
+
DescripcionDescuentooRecargo: "Descuento",
|
|
162
|
+
IndicadorFacturacionDescuentooRecargo: "ITBIS1_18Percent",
|
|
163
|
+
},
|
|
164
|
+
],
|
|
165
|
+
},
|
|
166
|
+
});
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
return (
|
|
170
|
+
<div>
|
|
171
|
+
<button onClick={handleSend} disabled={mutation.isPending}>
|
|
172
|
+
{mutation.isPending ? 'Enviando...' : 'Enviar ECF'}
|
|
173
|
+
</button>
|
|
174
|
+
{mutation.isSuccess && <p>ECF enviado exitosamente!</p>}
|
|
175
|
+
{mutation.isError && <p>Error: {mutation.error.message}</p>}
|
|
176
|
+
</div>
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
## Entornos
|
|
182
|
+
|
|
183
|
+
| Entorno | URL |
|
|
184
|
+
|---------|-----|
|
|
185
|
+
| `test` | `https://api.test.ecfx.ssd.com.do` |
|
|
186
|
+
| `cert` | `https://api.cert.ecfx.ssd.com.do` |
|
|
187
|
+
| `prod` | `https://api.prod.ecfx.ssd.com.do` |
|
|
188
|
+
|
|
189
|
+
## Referencia de la API
|
|
190
|
+
|
|
191
|
+
### `createEcfReactClient(config)`
|
|
192
|
+
|
|
193
|
+
Crea un cliente tipado de React Query para la API de ECF DGII.
|
|
194
|
+
|
|
195
|
+
**Opciones de configuración:**
|
|
196
|
+
|
|
197
|
+
| Opción | Tipo | Requerido | Descripción |
|
|
198
|
+
|--------|------|-----------|-------------|
|
|
199
|
+
| `apiKey` | `string` | Sí | Tu API key para autenticación |
|
|
200
|
+
| `environment` | `'test' \| 'cert' \| 'prod'` | No | Entorno destino (por defecto: `'test'`) |
|
|
201
|
+
| `baseUrl` | `string` | No | URL base personalizada (sobreescribe `environment`) |
|
|
202
|
+
|
|
203
|
+
**Retorna:** `{ $api, fetchClient }`
|
|
204
|
+
|
|
205
|
+
- `$api` - El cliente openapi-react-query con `useQuery`, `useMutation`, `useSuspenseQuery`, etc.
|
|
206
|
+
- `fetchClient` - El cliente openapi-fetch subyacente para uso fuera de React.
|
|
207
|
+
|
|
208
|
+
## Arquitectura Backend / Frontend
|
|
209
|
+
|
|
210
|
+
El SDK de React está diseñado para el lado del **frontend** de la arquitectura recomendada:
|
|
211
|
+
|
|
212
|
+
1. Tu **backend** valida, guarda y convierte tu factura interna al formato ECF, luego la envía a ECF SSD usando su token principal
|
|
213
|
+
2. Tu **backend** expone un endpoint (ej. `GET /api/v1/ecf-token`) que genera un **API key de solo lectura** con alcance al tenant/RNC a través del endpoint `/apikey` de ECF SSD
|
|
214
|
+
3. Tu **frontend** almacena este token de forma segura, lo renueva ante `401` o expiración, y lo usa con `@ssddo/ecf-react` para consultar el estado de los ECF directamente
|
|
215
|
+
|
|
216
|
+
```tsx
|
|
217
|
+
// Gestión de token — tu hook personalizado
|
|
218
|
+
// Llama al endpoint /api/v1/ecf-token de tu backend, almacena el token de forma segura,
|
|
219
|
+
// y lo renueva automáticamente cuando expira o recibe un 401
|
|
220
|
+
const ecfToken = useEcfToken();
|
|
221
|
+
|
|
222
|
+
const { $api } = createEcfReactClient({
|
|
223
|
+
apiKey: ecfToken, // solo lectura, con alcance al tenant/RNC
|
|
224
|
+
environment: 'prod',
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
function EstadoEcf({ rnc, encf }: { rnc: string; encf: string }) {
|
|
228
|
+
// Consulta ECF SSD directamente — no se necesita proxy en el backend
|
|
229
|
+
const { data } = $api.useQuery('get', '/ecf/{rnc}/{encf}', {
|
|
230
|
+
params: { path: { rnc, encf } },
|
|
231
|
+
refetchInterval: 3000,
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
if (data?.progress === 'Finished') {
|
|
235
|
+
return (
|
|
236
|
+
<div>
|
|
237
|
+
<p>Comprobante aceptado</p>
|
|
238
|
+
<p>Código seguridad: {data.codSec}</p>
|
|
239
|
+
<QRCode value={data.impresionUrl} />
|
|
240
|
+
</div>
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return <p>Procesando... ({data?.progress})</p>;
|
|
245
|
+
}
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
Este patrón descarga el polling de tu backend y permite que el frontend se comunique directamente con ECF SSD usando un token restringido. Consulta el [README principal](../README.md#arquitectura-backend--frontend) para el diagrama completo y ejemplo del backend.
|
|
249
|
+
|
|
250
|
+
## Uso fuera de React
|
|
251
|
+
|
|
252
|
+
Para aplicaciones del lado del servidor o sin React, usa el SDK base de TypeScript: [`@ssddo/ecf-sdk`](https://www.npmjs.com/package/@ssddo/ecf-sdk).
|