Fotos¶
Disponible para: Owner, Project Manager, Supervisor, Badge Worker (solo Jobs asignados)
Las fotos pueden adjuntarse a Bids y Jobs para documentar condiciones del sitio, avance, materiales y cualquier registro visual relevante al proyecto.
Dónde pueden adjuntarse fotos¶
Las fotos se admiten en dos tipos de entidad:
- Bids — para documentar condiciones previas al trabajo, mediciones o material de apoyo.
- Jobs — para documentar trabajo en curso, estado de terminación o problemas encontrados.
Actualmente no hay fotos directamente en Invoices ni en Updates. Para adjuntar una foto a una actualización, publíquela como foto de Bid o Job junto con la actualización.
Restricciones de carga¶
| Constraint | Limit |
|---|---|
| Tipos de archivo permitidos | JPEG, PNG, WEBP, HEIC |
| Tamaño máximo por archivo | 15 MB |
| Máximo de archivos por carga | 10 archivos por solicitud |
Las cargas que superen 15 MB por archivo o 10 archivos por solicitud serán rechazadas con error de validación. Divida lotes grandes en varias cargas.
Conversión automática HEIC¶
HEIC es el formato predeterminado en iPhones modernos. Hardhat Flow acepta HEIC pero los convierte automáticamente:
- El archivo HEIC original se sube a DigitalOcean Spaces.
- Una tarea en segundo plano de Celery procesa la conversión.
- El archivo se convierte a JPEG y se almacena, actualizando la referencia en el registro de la foto.
- El HEIC original se elimina del almacenamiento tras una conversión exitosa.
La conversión HEIC es asíncrona. La foto puede mostrarse brevemente como “procesando” tras la carga. Actualice para ver el JPEG convertido.
Generación de miniaturas¶
Para cada foto cargada (tras cualquier conversión HEIC), se genera una miniatura de forma asíncrona:
- Miniaturas de 400 px de ancho, alto proporcional al aspecto original.
- Generadas por una tarea worker de Celery — no durante la solicitud de carga.
- Almacenadas en DigitalOcean Spaces junto al archivo a resolución completa.
- Usadas en vistas de lista y paneles de vista previa para reducir tiempos de carga.
Los archivos a resolución completa siempre se conservan. Las miniaturas son solo para visualización.
Quién puede cargar¶
| Role | Puede cargar en Bids | Puede cargar en Jobs |
|---|---|---|
| Owner | Sí — cualquier Bid | Sí — cualquier Job |
| Project Manager | Sí — sus Bids asignados | Sí — sus Jobs asignados |
| Supervisor | No | Sí — sus Jobs asignados |
| Badge Worker | No | Sí — Jobs a los que está asignado |
| Accounting | No | No |
Quién puede eliminar¶
La eliminación de fotos depende de la autoría y el rol:
- Los autores pueden eliminar sus propias fotos en cualquier momento.
- Los Owners pueden eliminar cualquier foto sin importar quién la subió.
- Ningún otro rol puede eliminar fotos.
Todas las eliminaciones de fotos en Hardhat Flow son eliminación lógica. El archivo no se quita de DigitalOcean Spaces — solo el registro en base de datos se marca como eliminado. Solo Platform Admin puede iniciar eliminación lógica.
Comportamiento de eliminación lógica¶
Cuando una foto se elimina de forma lógica:
- El registro en base de datos recibe marca
deleted_at. - El registro ya no aparece en respuestas de API ni en la interfaz.
- El archivo permanece en DigitalOcean Spaces y no se purga.
- La eliminación queda en el ActivityLog.
Este enfoque permite recuperar fotos si la eliminación fue por error y mantiene la auditoría del almacenamiento.
La eliminación física del archivo en DigitalOcean Spaces requiere acción de Platform Admin y se maneja fuera del flujo normal de la aplicación.
Referencia de API¶
Listar fotos¶
GET /{tenant-slug}/api/v1/photos/{bids|jobs}/{entity-id}/
Devuelve una lista paginada de fotos no eliminadas para la entidad indicada.
Sobre de respuesta:
{
"count": 3,
"next": null,
"previous": null,
"results": [
{
"id": "uuid",
"entity_type": "job",
"entity_id": "uuid",
"uploaded_by_id": "uuid",
"uploaded_by_name": "Jane Doe",
"file_url": "https://...",
"thumbnail_url": "https://...",
"created_at": "2026-04-07T10:00:00Z"
}
]
}
thumbnail_url será cadena vacía justo después de la carga y se llenará cuando complete la tarea Celery.
Cargar fotos¶
POST /{tenant-slug}/api/v1/photos/{bids|jobs}/{entity-id}/
Content-Type: multipart/form-data
Campo: photos — uno o más archivos (máximo 10 por solicitud).
Errores de validación (400):
| Condition | Error message |
|---|---|
| Sin archivos | At least one file is required. |
| Más de 10 archivos | Maximum 10 files per upload request. |
| Tipo no admitido | Unsupported file type '...'. Allowed: JPEG, PNG, WEBP, HEIC. |
| Archivo mayor a 15 MB | 'filename' exceeds the 15 MB file size limit. |
Éxito (201): Arreglo de objetos de foto creados (misma forma que en listado).
Eliminar una foto¶
DELETE /{tenant-slug}/api/v1/photos/{photo-id}/
Eliminación lógica de la foto. El registro en base de datos se marca como eliminado; el archivo en Spaces se conserva.
| Response | Condition |
|---|---|
204 No Content |
Éxito |
403 Forbidden |
El llamador no es el autor, Owner ni Platform Admin |
404 Not Found |
La foto no existe o ya fue eliminada |
Diseño de almacenamiento¶
Los archivos se guardan en DigitalOcean Spaces bajo esta estructura:
/{tenant-slug}/{entity-type}/{entity-id}/{photo-uuid}.{ext}
Las miniaturas están en:
/{tenant-slug}/{entity-type}/{entity-id}/thumbnails/{photo-uuid}.jpg