221 lines
6.5 KiB
Markdown
221 lines
6.5 KiB
Markdown
# 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://<DATABASE_NAME>.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 "<FCM_DEVICE_TOKEN>" --title "Hola" --body "Prueba"
|
||
```
|
||
|
||
Ejemplo de validación sin enviar (dry run):
|
||
|
||
```
|
||
node src/scripts/sendTestNotification.js --token "<FCM_DEVICE_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 "<FCM_DEVICE_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.
|
||
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=194.164.175.51
|
||
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 <JWT>` 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 <JWT>" \
|
||
-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 <JWT>` 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
|
||
```
|