feat: incorporar eCMR y mercancias desde 7fbfd30

Traslada los cambios de 7fbfd300b4ea17212c04528145d366a3f00b91e5 conservando la configuracion de entorno de produccion.
This commit is contained in:
2026-09-29 13:32:12 +02:00
parent fd3a63809b
commit dc545c9d86
10 changed files with 1110 additions and 2 deletions
+134
View File
@@ -0,0 +1,134 @@
# Consulta y firma eCMR desde la app
`GET /api/trips/:id/ecmr`
Enviar `Authorization: Bearer <token de login>` con la sesión de la app.
La respuesta contiene únicamente documentos activos con PDF generado de los
tramos cuyo DNI coincide con el conductor autenticado.
```json
{
"success": true,
"data": [
{
"id_documento": 7,
"n_proveedor": 2,
"numero": "ECMR-123",
"version": 1,
"estado": "emitido",
"fecha_creacion": "2026-09-16 10:00:00",
"document_url": "https://<erp>/adm/pages/logic/ecmr/documento.php?t=<token-publico>",
"firma_carga_completada": false,
"firma_descarga_completada": false,
"firma_carga_url": "https://<erp>/adm/pages/logic/ecmr/firmar.php?t=<token-carga>",
"firma_descarga_url": "https://<erp>/adm/pages/logic/ecmr/firmar.php?t=<token-descarga>",
"firma_expira_en_segundos": 900
}
]
}
```
## Integración React / React Native
En el detalle del viaje, consultar esta ruta y mostrar:
- «Ver eCMR»: abrir `document_url` mediante el visor PDF de la app.
- «Firmar carga»: abrir `firma_carga_url` en el navegador seguro o WebView con
soporte HTTPS y WebCrypto. Si es `null`, mostrar «Carga firmada».
- «Firmar descarga»: abrir `firma_descarga_url` de la misma manera. Si es `null`,
mostrar «Descarga firmada».
Los enlaces son independientes por documento y rol. Después de volver de la
pantalla de firma, consultar de nuevo la ruta para refrescar el estado. Los
campos `firma_*_completada` son booleanos; no inferir las firmas a partir del
estado general del viaje.
Las URLs de firma duran 15 minutos desde su emisión. Volver a consultar cuando
caduquen. Cada consulta crea invitaciones temporales para los roles pendientes:
no usar sondeo periódico ni precarga masiva. La respuesta lleva `no-store`.
No persistir ni registrar enlaces, y no enviar el Bearer de la app a las URLs
PHP. El token del enlace es la credencial temporal, no un parámetro `skip_otp`.
La pantalla PHP valida el acceso emitido por Node, comprueba que el conductor
continúa asignado al tramo y omite el código de seis cifras. El DNI procede de
la sesión y no se puede cambiar. Se mantienen nombre, consentimiento y firma
criptográfica. Las invitaciones de correo/WhatsApp conservan su OTP.
Tras firmar carga desde la app, se consulta la app para acceder a la firma de
descarga; no se envía un correo o WhatsApp automático en este canal.
Si `data` está vacío, mostrar «Este viaje todavía no tiene eCMR disponible».
El PDF se entrega como `attachment`; abrirlo en un navegador puede descargarlo.
Errores: 401 sin sesión/DNI, 404 identificador inválido o viaje inexistente,
403 conductor no asignado, 500 error de base de datos/invitaciones. No mostrar
los errores como ausencia de documentos. Un enlace PHP caducado o cuyo tramo
ya no está asignado devuelve 403 y pide volver a abrirlo desde la app.
## Requisitos
- Node y PHP deben usar las mismas tablas `ecmr_documentos`, `ecmr_invitaciones`,
`ecmr_firmas` y `c_viajes_proveedor`.
- Desplegar también `firma_app.php`, los cambios de `firma_api.php`, `firmar.php`,
`common.php` y las traducciones ES/EN del ERP.
- `public_url` debe apuntar al ERP actualizado y ser accesible mediante HTTPS.
- No requiere nuevas columnas ni migraciones para los enlaces de firma.
- No se ha facilitado el código React; queda pendiente conectar sus botones.
## Validación
- 40 pruebas Node: consulta eCMR, autorización, enlaces por rol, rollback y
regresión de listado, viaje activo y puntos intermedios.
- 27 comprobaciones PHP: DNI, caducidad, asignación, OTP y traducciones.
- Prueba del JavaScript de firma para sesiones app y enlaces con OTP.
- Consultas SQL reales e invitación temporal verificadas en desarrollo mediante
una transacción revertida. No se ha firmado ningún documento real.
## QR del PDF DeCA / eCMR
`GET /api/trips/:id/ecmr/:documentId/qr`
Usar el mismo `Authorization: Bearer <token de login>` y el `id_documento`
obtenido en la consulta eCMR del viaje. Se especifica el documento porque un
viaje puede tener varios tramos y versiones.
```json
{
"success": true,
"data": {
"id_documento": 7,
"n_proveedor": 2,
"numero": "ECMR-123",
"version": 1,
"document_url": "https://<erp>/adm/pages/logic/ecmr/documento.php?t=<token-publico>",
"qr_data_url": "data:image/png;base64,..."
}
}
```
La imagen PNG es de 320 × 320 píxeles y codifica exactamente `document_url`,
la URL del PDF usada por el QR del documento. No codifica un enlace de firma.
En React Native puede mostrarse con:
```jsx
<Image source={{ uri: data.qr_data_url }} style={{ width: 240, height: 240 }} />
```
Solo devuelve documentos activos con PDF generado y del tramo asignado al DNI
de la sesión. Responde `401` sin sesión/DNI y `404` si los identificadores no son
válidos, el documento no existe, pertenece a otro viaje/conductor, está sustituido
o no tiene PDF generado. El `404` evita revelar documentos ajenos. Los fallos de
BD o generación del QR devuelven `500` con un mensaje genérico.
La respuesta lleva `Cache-Control: no-store`. Esta consulta no crea invitaciones,
no modifica la BD ni envía mensajes; se puede usar para actualizar la imagen sin
renovar enlaces de firma. Al sustituirse una versión, volver a consultar los
documentos del viaje y utilizar el nuevo identificador. El QR contiene un enlace
público al PDF: no registrarlo en logs ni analítica.
Despliegue: instalar las dependencias del lockfile (`npm ci --omit=dev`) y
reiniciar el proceso Node según el procedimiento habitual. Se añade `qrcode`
1.5.4. No requiere migraciones de BD ni cambios en PHP.
Pruebas: `node --test test/trips.ecmr.integration.test.js` cubre autenticación,
identificadores, filtrado por tramo/conductor, ausencia de documentos, PNG real,
URL codificada, errores de BD/generador y regresión del flujo de firma.