# tablas que tenemos que actualizar en la base de datos de producción c_cambios_estado c_cambios_estado_eliminados c_viajes_puntos c_viajes_puntos_tracking c_viajes_incidencias v_viajes_incidencias trg_c_viajes_puntos_tracking_au trg_c_cambios_estado_bd # Node Gestión API – Firebase Admin Este proyecto incluye un script de prueba para enviar notificaciones con Firebase Admin SDK. ## Configuración segura de credenciales La opción más segura para persistir `GOOGLE_APPLICATION_CREDENTIALS` en Linux es usar un archivo de entorno protegido y cargarlo desde tu gestor de procesos (systemd o PM2). Ejemplo con systemd: 1. Crea un archivo de entorno fuera del repo, con permisos restrictivos: ``` # /etc/node-gestion-api.env GOOGLE_APPLICATION_CREDENTIALS=/var/www/node.gestion.abianservice.com/notabser-firebase-adminsdk-fbsvc-bf88758663.json FIREBASE_DATABASE_URL=https://.firebaseio.com ``` ``` sudo chown root:root /etc/node-gestion-api.env sudo chmod 600 /etc/node-gestion-api.env ``` 2. En tu servicio systemd: ``` EnvironmentFile=/etc/node-gestion-api.env ``` Si trabajas localmente, puedes usar `.env` (ya soportado por `dotenv`) y mantenerlo fuera del control de versiones. ## Script de prueba (FCM) El script está en `src/scripts/sendTestNotification.js`. Variables esperadas (pueden ir en `.env`): - `GOOGLE_APPLICATION_CREDENTIALS` (ruta absoluta) - `FIREBASE_DATABASE_URL` (opcional) - `FCM_TEST_TOKEN` - `FCM_TEST_TITLE` (opcional) - `FCM_TEST_BODY` (opcional) ## Uso rápido Ejemplo de envío real: ``` node src/scripts/sendTestNotification.js --token "" --title "Hola" --body "Prueba" ``` Ejemplo de validación sin enviar (dry run): ``` node src/scripts/sendTestNotification.js --token "" --dry-run ``` Self-test de inicialización (no envía): ``` node src/scripts/sendTestNotification.js --self-test ``` También puedes usar el script de npm: ``` npm run notify:test -- --token "" --dry-run ``` ## Fotos de estado de viaje (almacenamiento temporal dual) Para `POST /api/trips/:id/status`, las fotos pueden guardarse en local y replicarse por SFTP en paralelo. - `TRIP_STATUS_UPLOAD_DIR` (opcional): base local. Admite una ruta o varias rutas candidatas separadas por `;`; si son relativas, se resuelven desde la raiz del proyecto Node. Default: `uploads/trips/status` dentro del proyecto. - `TRIP_STATUS_PHOTO_STORAGE_MODE` (opcional): `local` o `dual`. Default: `local`. - `TRIP_STATUS_SFTP_HOST`: host SFTP remoto. - `TRIP_STATUS_SFTP_PORT` (opcional): puerto SFTP. Default: `22`. - `TRIP_STATUS_SFTP_USERNAME`: usuario SFTP. - `TRIP_STATUS_SFTP_PASSWORD`: password SFTP. - `TRIP_STATUS_SFTP_REMOTE_BASE_DIR`: directorio base remoto. Ejemplo de modo temporal dual: ```bash TRIP_STATUS_PHOTO_STORAGE_MODE=dual TRIP_STATUS_SFTP_HOST=localhost TRIP_STATUS_SFTP_PORT=22 TRIP_STATUS_SFTP_USERNAME=ssh_fotos_estado TRIP_STATUS_SFTP_PASSWORD=******** TRIP_STATUS_SFTP_REMOTE_BASE_DIR=/var/www/vhosts/gestion.abianservice.com/httpdocs/produccion/app/fotos_estado_react_native ``` ## API: Crear incidencia de viaje Endpoint: `POST /api/trips/:tripId/incidencias` Auth: `Bearer ` obligatorio. ### Request JSON ```json { "incidencia": "Retraso por tráfico", "notificar": 0, "notificar_cr": 0 } ``` Notas: - `incidencia` es obligatoria (string, `trim`, no vacía). - `notificar`/`notificar_cr` se ignoran; backend fuerza `ind_aviso_cli=1` y `ind_aviso_cr=1`. ### Responses - `201`: - `{ "success": true, "message": "correcto" }` - `{ "success": true, "message": "correcto", "warning": "email_failed" }` (si falla SMTP, sin revertir insert) - `400`: payload inválido (`tripId` inválido o `incidencia` vacía) - `401`: no autenticado - `403`: usuario autenticado sin acceso al viaje - `404`: viaje no existe - `500`: error interno ### cURL ```bash curl -X POST "http://127.0.0.1:3001/api/trips/248230/incidencias" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "incidencia": "Cliente no localizable en punto de recogida", "notificar": 0, "notificar_cr": 0 }' ``` ## API: Estado automático de viaje (auditoría) Endpoint: `POST /api/trips/:id/auto-status` Auth: `Bearer ` obligatorio. ### Request JSON ```json { "id_estado": 5, "id_punto": 8123, "observaciones": "Estado actualizado automáticamente", "ind_fallido": 0, "latitud": "40.416775", "longitud": "-3.703790", "fecha_y_hora": "2026-02-17 12:34:56" } ``` Notas: - `id_estado` es obligatorio. - `id_punto` es opcional. - Si se envía `id_punto`, `id_estado` solo puede ser `3`, `4` o `5`. - Sin `id_punto`, se permite cualquier estado válido en `t_viaje_estados`. - `fecha_y_hora` es opcional: si no es válida se usa hora servidor. - No se soportan fotos (`fotos_concat`/multipart). - Este endpoint no actualiza `c_viajes.id_estado`. - Siempre inserta en `c_cambios_estado` con `actualizado_automaticamente=1`. - Si hay `id_punto`, también actualiza `c_viajes_puntos` con `actualizado_automaticamente=1` para disparar `trg_c_viajes_puntos_tracking_au`. ### Responses - `200`: estado automático registrado - `400`: payload inválido - `401`: no autenticado - `403`: usuario autenticado sin acceso al viaje - `404`: viaje o punto no existe - `422`: `id_punto` enviado con `id_estado` fuera de `3/4/5` - `500`: error interno ## Driver license seguro (backend) Este backend soporta carga y acceso seguro de carnet de conducir: - `POST /api/update_driver_license` (alias: `POST /api/upload_driver_license`) - No usar `POST /update_profile_photo` para carnet: esa ruta es solo para `foto_perfil` y ahora rechaza payloads de `driver_license`. - `GET /api/secure/driver-license/side/:side?dni=...&id_proveedor=...` (`side`: `front|back`) - `GET /api/secure/driver-license/:publicId` (compatibilidad) - `DELETE /api/secure/driver-license/:publicId` (borrado lógico) ### Variables de entorno nuevas - `DRIVER_LICENSE_ENCRYPTION_KEY` (obligatoria): clave AES-256-GCM de 32 bytes (`hex` de 64 chars o `base64`, opcional prefijo `base64:`). - `DRIVER_LICENSE_KEY_VERSION` (opcional, default `v1`). - `DRIVER_LICENSE_STORAGE_DIR` (opcional, default `./secure_storage/driver-license`). - `DRIVER_LICENSE_RETENTION_DAYS` (opcional, default `365`). - `DRIVER_LICENSE_PURGE_BATCH_SIZE` (opcional, default `100`). ### Migración SQL Aplicar: ``` mysql -h "$DB_HOST" -u "$DB_USER" -p"$DB_PASSWORD" "$DB_NAME" < src/sql/migrations/20260216_driver_license_security.sql mysql -h "$DB_HOST" -u "$DB_USER" -p"$DB_PASSWORD" "$DB_NAME" < src/sql/migrations/20260216_driver_license_sides.sql ``` ### Purga física por retención ``` npm run purge:driver-licenses ```