# Mercancía del viaje desde la app Base: la misma URL de API usada por el login y los viajes. Todas las peticiones requieren `Authorization: Bearer `. POST y PUT requieren `Content-Type: application/json`. Solo el conductor asignado al viaje puede consultar, añadir o editar su mercancía. La mercancía pertenece al viaje completo y la comparten sus tramos. Las operaciones se limitan al identificador de viaje solicitado. ## Consultar mercancía y tipos de embalaje `GET /api/trips/:id/cargo` ```json { "success": true, "data": { "mercancias": [ { "id_mercancia": 123, "id_tipo_embalaje": 1, "cantidad": 2, "peso": 400, "largo": 120, "ancho": 80, "alto": 150, "remontable": true, "tipo_embalaje": "Palet", "tipo_embalaje_en": "Pallet" } ], "embalajes": [ { "id_tipo_embalaje": 1, "tipo_embalaje": "Palet", "tipo_embalaje_en": "Pallet" } ], "observaciones_mercancia": "Material", "ecmr_emitido": true } } ``` Los nombres de embalaje del ejemplo son ilustrativos: usar los valores e IDs que devuelva el servidor. `embalajes` contiene las opciones activas. `observaciones_mercancia` es información del viaje, de solo lectura en estos endpoints. Una lista `mercancias: []` permite añadir la primera línea. ## Añadir una línea `POST /api/trips/:id/cargo` ```json { "id_tipo_embalaje": 1, "cantidad": 2, "peso": 400, "largo": 120, "ancho": 80, "alto": 150, "remontable": true } ``` ## Editar una línea `PUT /api/trips/:id/cargo/:cargoId` Mismo cuerpo que POST. `cargoId` es el `id_mercancia` devuelto por GET. Enviar la línea completa; no es una actualización parcial. La línea debe pertenecer al viaje indicado. Las demás líneas se conservan. | Campo | Regla | |---|---| | `id_tipo_embalaje` | Entero obligatorio; debe existir y estar activo. En edición se permite conservar el embalaje anterior aunque esté inactivo. | | `cantidad` | Entero obligatorio mayor que cero; número de bultos. | | `peso` | Entero obligatorio, de 0 a 9999999999; **peso total de la línea en kg**, no peso por bulto. | | `largo`, `ancho`, `alto` | Enteros en centímetros, entre 0 y 2147483647. Si se omiten se guardan a cero. | | `remontable` | Booleano; también se acepta 0/1. Si se omite se guarda como falso. | Los números deben enviarse como números JSON, no cadenas. El almacenamiento actual no admite decimales en peso o medidas; no redondear silenciosamente. Para emitir un eCMR el peso total del viaje debe ser mayor que cero. ## Respuesta de guardado POST devuelve HTTP 201; PUT devuelve HTTP 200. ```json { "success": true, "data": { "id_mercancia": 123, "id_tipo_embalaje": 1, "cantidad": 2, "peso": 400, "largo": 120, "ancho": 80, "alto": 150, "remontable": true, "tipo_embalaje": "Palet", "tipo_embalaje_en": "Pallet" }, "ecmr_documentos_actualizados": 1, "ecmr_versiones": [ { "id_documento": 45, "numero": "ECMR-VIAJE-P1-V3", "version": 3, "public_url": "https:///adm/pages/logic/ecmr/documento.php?t=", "n_proveedor": 1 } ] } ``` Al añadir o editar se genera una nueva versión y un nuevo PDF de **cada eCMR vigente del viaje**. Se conservan los extras documentales (remolque, ADR, aduanas, autorización especial e Incoterm). Las versiones y firmas antiguas permanecen en el historial. Las nuevas versiones requieren nuevas firmas; los enlaces de firma de versiones sustituidas dejan de ser válidos. `ecmr_documentos_actualizados` cuenta todas las revisiones del viaje. `ecmr_versiones` solo expone los documentos de los tramos asignados al conductor. Si todavía no hay eCMR, la mercancía se guarda y se devuelve 0 y `[]`; la primera emisión continúa haciéndose desde el ERP. El guardado y las revisiones se realizan en una misma transacción. Si faltan datos para emitir o falla un PDF, no se confirma el cambio de mercancía. Se marca el viaje como editado desde la app. No se envían invitaciones por correo o WhatsApp automáticamente en esta operación. ## Flujo recomendado del frontend 1. Abrir «Mercancía» desde el detalle del viaje y cargar GET. 2. Mostrar líneas y botones «Añadir mercancía» / «Editar». 3. Usar los embalajes recibidos y convertir los campos numéricos antes de enviar. 4. Si `ecmr_emitido` es verdadero, avisar antes de guardar: «Se generará una nueva versión del eCMR y será necesario firmarla de nuevo». 5. Deshabilitar Guardar durante la petición; no reenviar automáticamente POST ante un timeout, para evitar duplicar líneas. Consultar el estado primero. 6. Tras el éxito, refrescar mercancía y `GET /api/trips/:id/ecmr` para obtener los nuevos PDFs y enlaces de firma. Desechar enlaces antiguos. 7. Mostrar los errores conservando los datos del formulario para corregirlos. ## Errores Formato: `{ "success": false, "error": "..." }`. Una validación de campo puede añadir `field`, por ejemplo `"field": "peso"`. - 401: sesión ausente, inválida o sin DNI. Usar el flujo de login existente. - 403: el conductor no tiene asignado el viaje. - 404: viaje o línea inexistente, o línea de otro viaje. - 422: datos inválidos, embalaje no permitido o datos insuficientes para emitir el eCMR. El cambio no se ha guardado. - 500: fallo al guardar o generar documentos. No presentar éxito ni suponer que no existen mercancías. No existe operación de eliminación en esta integración. ## Operación del backend Node invoca un proceso PHP local, sin shell y con JSON por stdin, para reutilizar el generador del ERP y mantener una única transacción de mercancía/documentos. El script solo funciona en CLI, vuelve a comprobar la asignación y exige que la base de datos coincida con `DB_NAME` de Node. No admite acceso HTTP. La ruta del generador está en `src/services/tripCargoWriter.js`; esta instalación usa `/var/www/desarrollogestion/desarrollo/adm/pages/logic/ecmr/mercancia_app_cli.php`. No se necesitan migraciones. El usuario del proceso Node debe poder ejecutar PHP, leer el ERP y escribir los PDFs en su repositorio eCMR. Validación: 73 pruebas Node y prueba de integración PHP sobre desarrollo con altas, ediciones, PDF real, versiones, permisos, fallo de emisión y rollback. Los datos y PDFs de prueba se revierten/eliminan.