Saltar a contenido

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:

  1. El archivo HEIC original se sube a DigitalOcean Spaces.
  2. Una tarea en segundo plano de Celery procesa la conversión.
  3. El archivo se convierte a JPEG y se almacena, actualizando la referencia en el registro de la foto.
  4. 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:

  1. El registro en base de datos recibe marca deleted_at.
  2. El registro ya no aparece en respuestas de API ni en la interfaz.
  3. El archivo permanece en DigitalOcean Spaces y no se purga.
  4. 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