4.0 Tests primero
Vitest: search.test.ts (10 — S-01..S-04), rls-audit.test.ts (42 —
3 intruders × 14 cross-collective ops proving zero leakage)
pgTAP: 007_search_tsvector.sql (9 — columns, GIN indexes, function
signature, generated-vector invariant)
Playwright: search.test.ts (2 — SR-01 happy-path, SR-02 filter toggle)
4.1 Búsqueda global
Migration 010_search_vectors.sql: STORED GENERATED tsvector columns
on shopping_items/tasks/notes + GIN indexes + search_result_type
enum + search_in_collective() SECURITY INVOKER function (RLS-aware,
trash-excluded per RN-08) + GRANT EXECUTE to anon/authenticated.
Uses `simple` config (no stemmer — bilingual en/es).
Store apps/web/src/lib/stores/search.ts — RPC wrapper
/search: debounced input (300ms, ≥2 chars), type filter chips with
multi-toggle, results grouped per type with data-testid hooks for
Playwright, deep-link per type (shopping_item → /lists/[id], task →
/tasks/[id], note → /notes/[id])
4.4 Security
rls-audit.test.ts replaces the curl-based manual audit with a
parametrised matrix — zero cross-collective reads/writes under
three different attacker roles
Realtime flake hardening
Add 250ms settle after SUBSCRIBED in realtime-helpers.ts — the Phoenix
socket reports SUBSCRIBED slightly before the backend finishes
propagating the filter to the WAL subscription, so mutations fired
immediately after subscribe could miss the filter under load.
4.Z Verification
just test-all → 211 verdes, 2 skipped
34 pgTAP (was 25, +9 search)
140 Vitest integration (was 88, +10 search +42 rls-audit; 2 skipped)
6 Vitest unit (unchanged)
31 Playwright (was 29, +2 search)
Deferred (prod-deploy scope):
PWA install/push (needs real iOS/Android hardware)
Kong rate-limit plugin config
JWT_SECRET / ANON_KEY rotation
Lighthouse PWA ≥ 90 manual validation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
162 lines
6.5 KiB
JSON
162 lines
6.5 KiB
JSON
{
|
|
"$schema": "https://inlang.com/schema/inlang-message-format",
|
|
"nav_lists": "Listas",
|
|
"nav_tasks": "Tareas",
|
|
"nav_notes": "Notas",
|
|
"nav_search": "Buscar",
|
|
"nav_settings": "Ajustes",
|
|
"nav_collective": "Colectivo",
|
|
"app_name": "Colectivo",
|
|
"loading": "Cargando…",
|
|
"error_generic": "Algo salió mal. Por favor, inténtalo de nuevo.",
|
|
"login_title": "Accede a Colectivo",
|
|
"login_button": "Iniciar sesión",
|
|
"logout_button": "Cerrar sesión",
|
|
"auth_back_to_home": "Volver al inicio",
|
|
"onboarding_title": "Bienvenido a Colectivo",
|
|
"onboarding_subtitle": "Crea un nuevo colectivo o únete a uno existente.",
|
|
"onboarding_create_tab": "Crear",
|
|
"onboarding_join_tab": "Unirse",
|
|
"onboarding_create_collective": "Crear un colectivo",
|
|
"onboarding_join_collective": "Unirme con un enlace de invitación",
|
|
"onboarding_collective_name": "Nombre del colectivo",
|
|
"onboarding_collective_name_placeholder": "p. ej. Nuestro Hogar",
|
|
"onboarding_collective_emoji": "Icono",
|
|
"onboarding_invite_link_label": "Enlace de invitación",
|
|
"onboarding_invite_link_placeholder": "Pega aquí el enlace de invitación…",
|
|
"onboarding_create_btn": "Crear colectivo",
|
|
"onboarding_join_btn": "Unirse al colectivo",
|
|
"collective_name_label": "Nombre del colectivo",
|
|
"collective_name_placeholder": "p. ej. Nuestro Hogar",
|
|
"invitation_title": "Tienes una invitación",
|
|
"invitation_subtitle": "Te han invitado a unirte a un colectivo.",
|
|
"invitation_accept_btn": "Aceptar invitación",
|
|
"invitation_success": "¡Te has unido al colectivo!",
|
|
"invitation_invalid_link": "Ese no parece un enlace de invitación válido.",
|
|
"invitation_error_not_found": "Este enlace de invitación no es válido.",
|
|
"invitation_error_already_used": "Esta invitación ya ha sido utilizada.",
|
|
"invitation_error_expired": "Esta invitación ha caducado.",
|
|
"invitation_error_already_member": "Ya eres miembro de este colectivo.",
|
|
"invitation_error_unauthenticated": "Debes iniciar sesión para aceptar una invitación.",
|
|
"invitation_error_unknown": "Algo salió mal. Por favor, inténtalo de nuevo.",
|
|
"manage_title": "Gestionar colectivo",
|
|
"manage_members": "Miembros",
|
|
"manage_you": "tú",
|
|
"manage_remove": "Eliminar miembro",
|
|
"manage_invite_title": "Invitar a alguien",
|
|
"manage_invite_role": "Rol",
|
|
"manage_generate_link": "Generar enlace de invitación",
|
|
"manage_copy": "Copiar",
|
|
"manage_copied": "¡Copiado!",
|
|
"manage_link_expires": "El enlace caduca en 7 días.",
|
|
"role_admin": "Administrador",
|
|
"role_member": "Miembro",
|
|
"role_guest": "Invitado",
|
|
"settings_title": "Ajustes",
|
|
"settings_display_name": "Nombre visible",
|
|
"settings_saving": "Guardando…",
|
|
"settings_language": "Idioma",
|
|
"settings_avatar": "Avatar",
|
|
"settings_avatar_initials": "Iniciales",
|
|
"settings_avatar_emoji": "Emoji",
|
|
"settings_avatar_upload": "Foto",
|
|
"settings_avatar_upload_btn": "Subir foto…",
|
|
"settings_account": "Cuenta",
|
|
"settings_keycloak_link": "Cambiar correo o contraseña",
|
|
"settings_logout": "Cerrar sesión",
|
|
"settings_save": "Guardar cambios",
|
|
"settings_saved": "Cambios guardados",
|
|
"invite_member": "Invitar miembro",
|
|
"invite_email_label": "Correo electrónico",
|
|
"invite_send": "Enviar invitación",
|
|
"lists_title": "Listas de la compra",
|
|
"lists_new_list": "Crear",
|
|
"lists_no_lists": "Sin listas",
|
|
"lists_create_first": "Crea tu primera lista de la compra",
|
|
"lists_trash": "Papelera",
|
|
"list_status_active": "Lista activa",
|
|
"list_status_completed": "Completada",
|
|
"list_status_archived": "Archivada",
|
|
"list_name_placeholder": "p. ej. Compra semanal",
|
|
"list_items_empty": "Lista vacía",
|
|
"list_items_empty_hint": "Añade tu primer producto abajo",
|
|
"list_reset": "Reiniciar lista",
|
|
"list_archive": "Archivar",
|
|
"list_delete": "Mover a papelera",
|
|
"list_actions": "Acciones de lista",
|
|
"list_item_placeholder": "Añadir producto…",
|
|
"list_qty_label": "Cant.",
|
|
"list_unit_label": "Unidad",
|
|
"list_add_item": "Añadir producto…",
|
|
"list_to_buy": "Por comprar",
|
|
"list_checked": "Marcado",
|
|
"list_start_session": "Comprar",
|
|
"list_finish_shopping": "Terminar compra",
|
|
"list_finish_confirm": "¿Marcar esta lista como completada?",
|
|
"list_finish_confirm_yes": "Sí, terminar",
|
|
"list_finish_confirm_no": "Seguir comprando",
|
|
"tasks_title": "Tareas",
|
|
"tasks_no_lists": "Sin listas de tareas",
|
|
"tasks_create_first": "Crea tu primera lista de tareas",
|
|
"tasks_new_list_placeholder": "Nueva lista de tareas",
|
|
"tasks_new_task_placeholder": "Añadir tarea",
|
|
"tasks_empty": "Sin tareas",
|
|
"tasks_empty_hint": "Añade tu primera tarea abajo",
|
|
"tasks_completed_section": "Completadas",
|
|
"tasks_pending_section": "Por hacer",
|
|
"tasks_completed_by": "Completada por {name}",
|
|
"tasks_toggle": "Alternar tarea",
|
|
"tasks_uncheck": "Desmarcar tarea",
|
|
"tasks_delete": "Eliminar tarea",
|
|
"tasks_delete_list": "Eliminar lista",
|
|
"tasks_delete_list_confirm": "¿Eliminar esta lista y todas sus tareas?",
|
|
"notes_title": "Notas",
|
|
"notes_new": "Nueva nota",
|
|
"notes_pinned_section": "Fijadas",
|
|
"notes_active_section": "Notas",
|
|
"notes_archive": "Archivar",
|
|
"notes_archive_view": "Notas archivadas",
|
|
"notes_archived_empty": "Sin notas archivadas",
|
|
"notes_trash_view": "Papelera de notas",
|
|
"notes_trash_empty": "La papelera está vacía",
|
|
"notes_pin": "Fijar nota",
|
|
"notes_unpin": "Desfijar nota",
|
|
"notes_unarchive": "Desarchivar nota",
|
|
"notes_send_to_trash": "Enviar a la papelera",
|
|
"notes_restore": "Restaurar",
|
|
"notes_duplicate": "Duplicar",
|
|
"notes_color": "Color",
|
|
"notes_title_placeholder": "Título",
|
|
"notes_content_placeholder": "Empieza a escribir…",
|
|
"notes_content_aria": "Contenido",
|
|
"notes_back_to_board": "Volver al tablero",
|
|
"notes_saved": "Guardado",
|
|
"notes_saving": "Guardando…",
|
|
"notes_empty_board": "Sin notas",
|
|
"notes_empty_board_hint": "Haz clic en \"Nueva nota\" para empezar",
|
|
"search_title": "Buscar",
|
|
"search_placeholder": "Busca listas, tareas, notas…",
|
|
"search_filter_all": "Todo",
|
|
"search_filter_lists": "Listas",
|
|
"search_filter_tasks": "Tareas",
|
|
"search_filter_notes": "Notas",
|
|
"search_group_shopping_items": "Productos",
|
|
"search_group_tasks": "Tareas",
|
|
"search_group_notes": "Notas",
|
|
"search_empty": "Sin resultados",
|
|
"search_hint": "Escribe al menos dos caracteres",
|
|
"trash_restore": "Restaurar",
|
|
"trash_delete_permanent": "Eliminar definitivamente",
|
|
"trash_empty": "La papelera está vacía",
|
|
"trash_expires_in": "Expira en {days} días",
|
|
"confirm_delete": "¿Eliminar?",
|
|
"cancel": "Cancelar",
|
|
"save": "Guardar",
|
|
"action_delete": "Eliminar",
|
|
"edit": "Editar",
|
|
"add": "Añadir",
|
|
"done": "Listo",
|
|
"sync_offline": "Sin conexión — los cambios se sincronizarán al reconectar",
|
|
"sync_syncing": "Sincronizando…"
|
|
}
|